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

*/ */
import { getJSON, post, RequestData } from '../helpers/request'; import { getJSON, post, RequestData } from '../helpers/request';


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

export function getCurrentUser(): Promise<any> { export function getCurrentUser(): Promise<any> {
return getJSON('/api/users/current'); return getJSON('/api/users/current');
} }
return getJSON('/api/users/groups', data); return getJSON('/api/users/groups', data);
} }


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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 GlobalFooterContainer from './GlobalFooterContainer';
import NavBar from '../../components/nav/NavBar'; 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() { componentDidMount() {
const html = document.querySelector('html'); const html = document.querySelector('html');
if (html) { if (html) {
return ( return (
<div className="global-container"> <div className="global-container">
<div className="page-wrapper" id="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="bd" className="page-wrapper-simple">
<div id="nonav" className="page-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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 OAuthProviders from './OAuthProviders';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { translate } from '../../../helpers/l10n'; 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); super(props);
this.state = { this.state = {
collapsed: props.identityProviders.length > 0, collapsed: props.identityProviders.length > 0,
}; };
} }


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


handleMoreOptionsClick = (event /*: Event */) => {
handleMoreOptionsClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault(); event.preventDefault();
this.setState({ collapsed: false }); 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() { render() {
return ( return (
<div id="login_form"> <div id="login_form">
id="login" id="login"
name="login" name="login"
className="login-input" className="login-input"
maxLength="255"
maxLength={255}
required={true} required={true}
autoFocus={true} autoFocus={true}
placeholder={translate('login')} placeholder={translate('login')}
value={this.state.login} value={this.state.login}
onChange={e => this.setState({ login: e.target.value })}
onChange={this.handleLoginChange}
/> />
</div> </div>


required={true} required={true}
placeholder={translate('password')} placeholder={translate('password')}
value={this.state.password} value={this.state.password}
onChange={e => this.setState({ password: e.target.value })}
onChange={this.handlePwdChange}
/> />
</div> </div>


<button name="commit" type="submit"> <button name="commit" type="submit">
{translate('sessions.log_in')} {translate('sessions.log_in')}
</button> </button>
<a className="spacer-left" href={window.baseUrl + '/'}>
<Link className="spacer-left" to="/">
{translate('cancel')} {translate('cancel')}
</a>
</Link>
</div> </div>
</div> </div>
</form> </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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 { connect } from 'react-redux';
import LoginForm from './LoginForm'; import LoginForm from './LoginForm';
import { doLogin } from '../../../store/rootActions'; import { doLogin } from '../../../store/rootActions';
import { getAppState } from '../../../store/rootReducer'; 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() { componentDidMount() {
this.mounted = true; this.mounted = true;
handleSuccessfulLogin = () => { handleSuccessfulLogin = () => {
const { location } = this.props; const { location } = this.props;
const queryReturnTo = location.query['return_to']; 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() { render() {
} }
} }


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


const mapDispatchToProps = { doLogin }; 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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 { connect } from 'react-redux';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import RecentHistory from '../../../app/components/RecentHistory';
import { doLogout } from '../../../store/rootActions'; import { doLogout } from '../../../store/rootActions';
import { translate } from '../../../helpers/l10n'; 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() { componentDidMount() {
this.props
.doLogout()
.then(() => {
this.props.doLogout().then(
() => {
RecentHistory.clear(); RecentHistory.clear();
window.location = window.baseUrl + '/';
})
.catch(() => {
/* do nothing */
});
window.location.href = getBaseUrl() + '/';
},
() => {}
);
} }


render() { render() {


const mapDispatchToProps = { doLogout }; 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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 { 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 ( return (
<section className="oauth-providers"> <section className="oauth-providers">
<ul> <ul>
{props.identityProviders.map(identityProvider => ( {props.identityProviders.map(identityProvider => (
<li key={identityProvider.key}> <li key={identityProvider.key}>
<a <a
href={`${window.baseUrl}/sessions/init/${identityProvider.key}`}
href={`${getBaseUrl()}/sessions/init/${identityProvider.key}`}
style={{ backgroundColor: identityProvider.backgroundColor }} style={{ backgroundColor: identityProvider.backgroundColor }}
// $FlowFixMe formatLabel is always defined through defaultProps
title={props.formatLabel(identityProvider.name)}>
title={formatLabel(identityProvider.name)}>
<img <img
alt={identityProvider.name} alt={identityProvider.name}
width="20" width="20"
height="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> </a>
</li> </li>
))} ))}
); );
} }


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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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'; 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>; return <SimpleContainer hideLoggedInInfo={true}>{children}</SimpleContainer>;
} }

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

*/ */
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n';


interface Props { interface Props {
location: { location: {


return ( return (
<div className="text-center"> <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"> <div className="big-spacer-top">
<Link to="/">Home</Link>
<Link to="/">{translate('layout.home')}</Link>
</div> </div>
</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

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * 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 { shallow } from 'enzyme';
import LoginForm from '../LoginForm'; import LoginForm from '../LoginForm';
import { change, click, submit } from '../../../../helpers/testUtils'; 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

login.login_to_sonarqube login.login_to_sonarqube
</h1> </h1>
<OAuthProviders <OAuthProviders
formatLabel={[Function]}
identityProviders={ identityProviders={
Array [ Array [
Object { Object {
login.login_to_sonarqube login.login_to_sonarqube
</h1> </h1>
<OAuthProviders <OAuthProviders
formatLabel={[Function]}
identityProviders={ identityProviders={
Array [ Array [
Object { Object {
autoFocus={true} autoFocus={true}
className="login-input" className="login-input"
id="login" id="login"
maxLength="255"
maxLength={255}
name="login" name="login"
onChange={[Function]} onChange={[Function]}
placeholder="login" placeholder="login"
> >
sessions.log_in sessions.log_in
</button> </button>
<a
<Link
className="spacer-left" className="spacer-left"
href="/"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
> >
cancel cancel
</a>
</Link>
</div> </div>
</div> </div>
</form> </form>
login.login_to_sonarqube login.login_to_sonarqube
</h1> </h1>
<OAuthProviders <OAuthProviders
formatLabel={[Function]}
identityProviders={ identityProviders={
Array [ Array [
Object { Object {
autoFocus={true} autoFocus={true}
className="login-input" className="login-input"
id="login" id="login"
maxLength="255"
maxLength={255}
name="login" name="login"
onChange={[Function]} onChange={[Function]}
placeholder="login" placeholder="login"
> >
sessions.log_in sessions.log_in
</button> </button>
<a
<Link
className="spacer-left" className="spacer-left"
href="/"
onlyActiveOnIndex={false}
style={Object {}}
to="/"
> >
cancel cancel
</a>
</Link>
</div> </div>
</div> </div>
</form> </form>

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

login.more_options=More options login.more_options=More options
login.login_with_x=Log in with {0} 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 # 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

<tr> <tr>
<td>assertText</td> <td>assertText</td>
<td>bd</td> <td>bd</td>
<td>*Reason : A functional error has happened*</td>
<td>*Reason: A functional error has happened*</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

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

<tr> <tr>
<td>waitForText</td> <td>waitForText</td>
<td>bd</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> </tr>
</tbody> </tbody>
</table> </table>

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

<tr> <tr>
<td>assertText</td> <td>assertText</td>
<td>bd</td> <td>bd</td>
<td>*Reason : A functional error has happened*</td>
<td>*Reason: A functional error has happened*</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

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

<tr> <tr>
<td>waitForText</td> <td>waitForText</td>
<td>bd</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> </tr>
</tbody> </tbody>
</table> </table>

Loading…
Cancel
Save