Browse Source

SONAR-9925 Rewrite session to typescript

tags/6.7-RC1
Grégoire Aubert 6 years ago
parent
commit
cb8126d2d0

+ 8
- 1
server/sonar-web/src/main/js/api/users.ts View File

@@ -19,6 +19,13 @@
*/
import { getJSON, post, RequestData } from '../helpers/request';

export interface IdentityProvider {
backgroundColor: string;
iconPath: string;
key: string;
name: string;
}

export function getCurrentUser(): Promise<any> {
return getJSON('/api/users/current');
}
@@ -43,7 +50,7 @@ export function getUserGroups(login: string, organization?: string): Promise<any
return getJSON('/api/users/groups', data);
}

export function getIdentityProviders(): Promise<any> {
export function getIdentityProviders(): Promise<{ identityProviders: IdentityProvider[] }> {
return getJSON('/api/users/identity_providers');
}


server/sonar-web/src/main/js/app/components/SimpleContainer.js → server/sonar-web/src/main/js/app/components/SimpleContainer.tsx View File

@@ -17,21 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import GlobalFooterContainer from './GlobalFooterContainer';
import NavBar from '../../components/nav/NavBar';

/*::
type Props = {
children?: React.Element<*> | Array<React.Element<*>>,
hideLoggedInInfo?: boolean
};
*/

export default class SimpleContainer extends React.PureComponent {
/*:: props: Props; */
interface Props {
children?: React.ReactNode;
hideLoggedInInfo?: boolean;
}

export default class SimpleContainer extends React.PureComponent<Props> {
componentDidMount() {
const html = document.querySelector('html');
if (html) {
@@ -50,7 +45,7 @@ export default class SimpleContainer extends React.PureComponent {
return (
<div className="global-container">
<div className="page-wrapper" id="container">
<NavBar className="navbar-global" id="global-navigation" height={30} />
<NavBar className="navbar-global" height={30} />

<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">

server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js → server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx View File

@@ -17,37 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import OAuthProviders from './OAuthProviders';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { translate } from '../../../helpers/l10n';
import { IdentityProvider } from '../../../api/users';

/*::
type Props = {
identityProviders: Array<{
backgroundColor: string,
iconPath: string,
key: string,
name: string
}>,
onSubmit: (string, string) => void
};
*/

/*::
type State = {
collapsed: boolean,
login: string,
password: string
};
*/
interface Props {
identityProviders: IdentityProvider[];
onSubmit: (login: string, password: string) => void;
}

export default class LoginForm extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */
interface State {
collapsed: boolean;
login: string;
password: string;
}

constructor(props /*: Props */) {
export default class LoginForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
collapsed: props.identityProviders.length > 0,
@@ -56,16 +45,22 @@ export default class LoginForm extends React.PureComponent {
};
}

handleSubmit = (event /*: Event */) => {
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
this.props.onSubmit(this.state.login, this.state.password);
};

handleMoreOptionsClick = (event /*: Event */) => {
handleMoreOptionsClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
this.setState({ collapsed: false });
};

handleLoginChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
this.setState({ login: event.currentTarget.value });

handlePwdChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
this.setState({ password: event.currentTarget.value });

render() {
return (
<div id="login_form">
@@ -97,12 +92,12 @@ export default class LoginForm extends React.PureComponent {
id="login"
name="login"
className="login-input"
maxLength="255"
maxLength={255}
required={true}
autoFocus={true}
placeholder={translate('login')}
value={this.state.login}
onChange={e => this.setState({ login: e.target.value })}
onChange={this.handleLoginChange}
/>
</div>

@@ -118,7 +113,7 @@ export default class LoginForm extends React.PureComponent {
required={true}
placeholder={translate('password')}
value={this.state.password}
onChange={e => this.setState({ password: e.target.value })}
onChange={this.handlePwdChange}
/>
</div>

@@ -127,9 +122,9 @@ export default class LoginForm extends React.PureComponent {
<button name="commit" type="submit">
{translate('sessions.log_in')}
</button>
<a className="spacer-left" href={window.baseUrl + '/'}>
<Link className="spacer-left" to="/">
{translate('cancel')}
</a>
</Link>
</div>
</div>
</form>

server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.js → server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.tsx View File

@@ -17,23 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import { connect } from 'react-redux';
import LoginForm from './LoginForm';
import { doLogin } from '../../../store/rootActions';
import { getAppState } from '../../../store/rootReducer';
import { getIdentityProviders } from '../../../api/users';
import { IdentityProvider, getIdentityProviders } from '../../../api/users';
import { getBaseUrl } from '../../../helpers/urls';

class LoginFormContainer extends React.PureComponent {
/*:: mounted: boolean; */
interface Props {
doLogin: (login: string, password: string) => Promise<void>;
location: { hash?: string; pathName: string; query: { return_to?: string } };
}

static propTypes = {
location: PropTypes.object.isRequired
};
interface State {
identityProviders?: IdentityProvider[];
}

state = {};
class LoginFormContainer extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = {};

componentDidMount() {
this.mounted = true;
@@ -51,14 +54,12 @@ class LoginFormContainer extends React.PureComponent {
handleSuccessfulLogin = () => {
const { location } = this.props;
const queryReturnTo = location.query['return_to'];
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${window.baseUrl}/`;
window.location = returnTo;
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${getBaseUrl()}/`;
window.location.href = returnTo;
};

handleSubmit = (login /*: string */, password /*: string */) => {
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {
/* do nothing */
});
handleSubmit = (login: string, password: string) => {
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {});
};

render() {
@@ -72,10 +73,10 @@ class LoginFormContainer extends React.PureComponent {
}
}

const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
appState: getAppState(state)
});

const mapDispatchToProps = { doLogin };

export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer);
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer as any);

server/sonar-web/src/main/js/apps/sessions/components/Logout.js → server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx View File

@@ -17,25 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { connect } from 'react-redux';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import RecentHistory from '../../../app/components/RecentHistory';
import { doLogout } from '../../../store/rootActions';
import { translate } from '../../../helpers/l10n';
import RecentHistory from '../../../app/components/RecentHistory';
import { getBaseUrl } from '../../../helpers/urls';

class Logout extends React.PureComponent {
interface Props {
doLogout: () => Promise<void>;
}

class Logout extends React.PureComponent<Props> {
componentDidMount() {
this.props
.doLogout()
.then(() => {
this.props.doLogout().then(
() => {
RecentHistory.clear();
window.location = window.baseUrl + '/';
})
.catch(() => {
/* do nothing */
});
window.location.href = getBaseUrl() + '/';
},
() => {}
);
}

render() {
@@ -52,4 +54,4 @@ const mapStateToProps = () => ({});

const mapDispatchToProps = { doLogout };

export default connect(mapStateToProps, mapDispatchToProps)(Logout);
export default connect(mapStateToProps, mapDispatchToProps)(Logout as any);

server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.js → server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx View File

@@ -17,43 +17,34 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
import { IdentityProvider } from '../../../api/users';
import { getBaseUrl } from '../../../helpers/urls';

/*::
type Props = {
formatLabel?: string => string,
identityProviders: Array<{
backgroundColor: string,
iconPath: string,
key: string,
name: string
}>
};
*/
interface Props {
formatLabel?: (name: string) => string;
identityProviders: IdentityProvider[];
}

export default function OAuthProviders(props /*: Props */) {
export default function OAuthProviders(props: Props) {
const formatLabel = props.formatLabel || defaultFormatLabel;
return (
<section className="oauth-providers">
<ul>
{props.identityProviders.map(identityProvider => (
<li key={identityProvider.key}>
<a
href={`${window.baseUrl}/sessions/init/${identityProvider.key}`}
href={`${getBaseUrl()}/sessions/init/${identityProvider.key}`}
style={{ backgroundColor: identityProvider.backgroundColor }}
// $FlowFixMe formatLabel is always defined through defaultProps
title={props.formatLabel(identityProvider.name)}>
title={formatLabel(identityProvider.name)}>
<img
alt={identityProvider.name}
width="20"
height="20"
src={window.baseUrl + identityProvider.iconPath}
src={getBaseUrl() + identityProvider.iconPath}
/>
<span>
{/* $FlowFixMe formatLabel is always defined through defaultProps */}
{props.formatLabel(identityProvider.name)}
</span>
<span>{formatLabel(identityProvider.name)}</span>
</a>
</li>
))}
@@ -62,6 +53,6 @@ export default function OAuthProviders(props /*: Props */) {
);
}

OAuthProviders.defaultProps = {
formatLabel: (name /*: string */) => translateWithParameters('login.login_with_x', name)
};
function defaultFormatLabel(name: string) {
return translateWithParameters('login.login_with_x', name);
}

server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.js → server/sonar-web/src/main/js/apps/sessions/components/SimpleSessionsContainer.tsx View File

@@ -17,16 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import SimpleContainer from '../../../app/components/SimpleContainer';

/*::
type Props = {
children?: React.Element<*> | Array<React.Element<*>>
};
*/
interface Props {
children?: React.ReactElement<any>;
}

export default function SimpleSessionsContainer({ children } /*: Props */) {
export default function SimpleSessionsContainer({ children }: Props) {
return <SimpleContainer hideLoggedInInfo={true}>{children}</SimpleContainer>;
}

+ 8
- 5
server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx View File

@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n';

interface Props {
location: {
@@ -33,14 +34,16 @@ export default function Unauthorized(props: Props) {

return (
<div className="text-center">
<p id="unauthorized">
{"You're not authorized to access this page. Please contact the administrator."}
</p>
<p id="unauthorized">{translate('unauthorized.message')}</p>

{!!message && <p className="spacer-top">Reason : {message}</p>}
{!!message && (
<p className="spacer-top">
{translate('unauthorized.reason')} {message}
</p>
)}

<div className="big-spacer-top">
<Link to="/">Home</Link>
<Link to="/">{translate('layout.home')}</Link>
</div>
</div>
);

server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js → server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.tsx View File

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import LoginForm from '../LoginForm';
import { change, click, submit } from '../../../../helpers/testUtils';

server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap → server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap View File

@@ -10,7 +10,6 @@ exports[`expands more options 1`] = `
login.login_to_sonarqube
</h1>
<OAuthProviders
formatLabel={[Function]}
identityProviders={
Array [
Object {
@@ -46,7 +45,6 @@ exports[`expands more options 2`] = `
login.login_to_sonarqube
</h1>
<OAuthProviders
formatLabel={[Function]}
identityProviders={
Array [
Object {
@@ -75,7 +73,7 @@ exports[`expands more options 2`] = `
autoFocus={true}
className="login-input"
id="login"
maxLength="255"
maxLength={255}
name="login"
onChange={[Function]}
placeholder="login"
@@ -114,12 +112,14 @@ exports[`expands more options 2`] = `
>
sessions.log_in
</button>
<a
<Link
className="spacer-left"
href="/"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
>
cancel
</a>
</Link>
</div>
</div>
</form>
@@ -136,7 +136,6 @@ exports[`logs in with identity provider 1`] = `
login.login_to_sonarqube
</h1>
<OAuthProviders
formatLabel={[Function]}
identityProviders={
Array [
Object {
@@ -188,7 +187,7 @@ exports[`logs in with simple credentials 1`] = `
autoFocus={true}
className="login-input"
id="login"
maxLength="255"
maxLength={255}
name="login"
onChange={[Function]}
placeholder="login"
@@ -227,12 +226,14 @@ exports[`logs in with simple credentials 1`] = `
>
sessions.log_in
</button>
<a
<Link
className="spacer-left"
href="/"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
>
cancel
</a>
</Link>
</div>
</div>
</form>

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1350,6 +1350,9 @@ login.login_to_sonarqube=Log In to SonarQube
login.more_options=More options
login.login_with_x=Log in with {0}

unauthorized.message=You're not authorized to access this page. Please contact the administrator.
unauthorized.reason=Reason:

#------------------------------------------------------------------------------
#
# USERS & GROUPS PAGE

+ 1
- 1
tests/src/test/resources/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html View File

@@ -36,7 +36,7 @@
<tr>
<td>assertText</td>
<td>bd</td>
<td>*Reason : A functional error has happened*</td>
<td>*Reason: A functional error has happened*</td>
</tr>
</tbody>
</table>

+ 1
- 1
tests/src/test/resources/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html View File

@@ -31,7 +31,7 @@
<tr>
<td>waitForText</td>
<td>bd</td>
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-base-id-provider' users are not allowed to sign up*</td>
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-base-id-provider' users are not allowed to sign up*</td>
</tr>
</tbody>
</table>

+ 1
- 1
tests/src/test/resources/user/OAuth2IdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html View File

@@ -36,7 +36,7 @@
<tr>
<td>assertText</td>
<td>bd</td>
<td>*Reason : A functional error has happened*</td>
<td>*Reason: A functional error has happened*</td>
</tr>
</tbody>
</table>

+ 1
- 1
tests/src/test/resources/user/OAuth2IdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html View File

@@ -31,7 +31,7 @@
<tr>
<td>waitForText</td>
<td>bd</td>
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-oauth2-id-provider' users are not allowed to sign up*</td>
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-oauth2-id-provider' users are not allowed to sign up*</td>
</tr>
</tbody>
</table>

Loading…
Cancel
Save