diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-09-20 12:05:07 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-09-21 20:20:55 +0200 |
commit | 64d76164768f4b74d6bdc714191f65567baf8d10 (patch) | |
tree | 91bb01ea5ea5c4449fe312a8196ed56ae0accbd6 | |
parent | d5fbab940ffbdc45df0c5a078a340caaf9fea956 (diff) | |
download | sonarqube-64d76164768f4b74d6bdc714191f65567baf8d10.tar.gz sonarqube-64d76164768f4b74d6bdc714191f65567baf8d10.zip |
rewrite account app in ts
17 files changed, 167 insertions, 149 deletions
diff --git a/server/sonar-web/src/main/js/api/users.ts b/server/sonar-web/src/main/js/api/users.ts index f266f79bd41..1954f192c50 100644 --- a/server/sonar-web/src/main/js/api/users.ts +++ b/server/sonar-web/src/main/js/api/users.ts @@ -29,8 +29,8 @@ export function changePassword(data: { login: string; password: string; previousPassword?: string; -}): Promise<void> { - return post('/api/users/change_password', data); +}) { + return post('/api/users/change_password', data).catch(throwGlobalError); } export interface UserGroup { diff --git a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx index 7a1e2bf2d3d..374be478fcd 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/StartupModal-test.tsx @@ -48,9 +48,11 @@ jest.mock('../../../helpers/dates', () => ({ })); const LOGGED_IN_USER: LoggedInUser = { + groups: [], isLoggedIn: true, login: 'luke', name: 'Skywalker', + scmAccounts: [], showOnboardingTutorial: false }; diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 254817b7430..4df5d148def 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -362,10 +362,13 @@ export interface LoggedInUser extends CurrentUser { email?: string; externalIdentity?: string; externalProvider?: string; + groups: string[]; homepage?: HomePage; isLoggedIn: true; + local?: boolean; login: string; name: string; + scmAccounts: string[]; } export interface LongLivingBranch extends Branch { diff --git a/server/sonar-web/src/main/js/apps/account/components/Account.js b/server/sonar-web/src/main/js/apps/account/components/Account.tsx index bb4e8645692..02711b9d407 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Account.js +++ b/server/sonar-web/src/main/js/apps/account/components/Account.tsx @@ -17,18 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { connect } from 'react-redux'; import Helmet from 'react-helmet'; import Nav from './Nav'; import UserCard from './UserCard'; -import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; +import { CurrentUser, LoggedInUser } from '../../../app/types'; +import { getCurrentUser, areThereCustomOrganizations, Store } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import '../account.css'; -class Account extends React.PureComponent { +interface Props { + currentUser: CurrentUser; + customOrganizations?: boolean; +} + +class Account extends React.PureComponent<Props> { componentDidMount() { if (!this.props.currentUser.isLoggedIn) { handleRequiredAuthentication(); @@ -49,8 +55,8 @@ class Account extends React.PureComponent { <Helmet defaultTitle={title} titleTemplate={'%s - ' + title} /> <header className="account-header"> <div className="account-container clearfix"> - <UserCard user={currentUser} /> - <Nav customOrganizations={this.props.customOrganizations} user={currentUser} /> + <UserCard user={currentUser as LoggedInUser} /> + <Nav customOrganizations={this.props.customOrganizations} /> </div> </header> @@ -60,7 +66,7 @@ class Account extends React.PureComponent { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: Store) => ({ currentUser: getCurrentUser(state), customOrganizations: areThereCustomOrganizations(state) }); diff --git a/server/sonar-web/src/main/js/apps/account/components/Nav.js b/server/sonar-web/src/main/js/apps/account/components/Nav.tsx index 8688985c311..304ddb10ded 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Nav.js +++ b/server/sonar-web/src/main/js/apps/account/components/Nav.tsx @@ -17,19 +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 { Link, IndexLink } from 'react-router'; import NavBarTabs from '../../../components/nav/NavBarTabs'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - customOrganizations: boolean -}; -*/ +interface Props { + customOrganizations?: boolean; +} -export default function Nav({ customOrganizations } /*: Props */) { +export default function Nav({ customOrganizations }: Props) { return ( <nav className="account-nav"> <NavBarTabs> diff --git a/server/sonar-web/src/main/js/apps/account/components/Password.js b/server/sonar-web/src/main/js/apps/account/components/Password.tsx index 00c12a5e67a..5834e143db4 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Password.js +++ b/server/sonar-web/src/main/js/apps/account/components/Password.tsx @@ -17,40 +17,42 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React, { Component } from 'react'; +import * as React from 'react'; import { changePassword } from '../../../api/users'; import { SubmitButton } from '../../../components/ui/buttons'; import { translate } from '../../../helpers/l10n'; +import { LoggedInUser } from '../../../app/types'; -export default class Password extends Component { - state = { - success: false, - errors: null +interface Props { + user: LoggedInUser; +} + +interface State { + errors?: string[]; + success: boolean; +} + +export default class Password extends React.Component<Props, State> { + oldPassword!: HTMLInputElement; + password!: HTMLInputElement; + passwordConfirmation!: HTMLInputElement; + state: State = { + success: false }; handleSuccessfulChange = () => { this.oldPassword.value = ''; this.password.value = ''; this.passwordConfirmation.value = ''; - this.setState({ success: true, errors: null }); - }; - - handleFailedChange = e => { - e.response.json().then(r => { - this.oldPassword.focus(); - this.setErrors(r.errors.map(e => e.msg)); - }); + this.setState({ success: true, errors: undefined }); }; - setErrors = errors => { - this.setState({ - success: false, - errors - }); + setErrors = (errors: string[]) => { + this.setState({ success: false, errors }); }; - handleChangePassword = e => { - e.preventDefault(); + handleChangePassword = (event: React.FormEvent) => { + event.preventDefault(); const { user } = this.props; const previousPassword = this.oldPassword.value; @@ -61,9 +63,10 @@ export default class Password extends Component { this.password.focus(); this.setErrors([translate('user.password_doesnt_match_confirmation')]); } else { - changePassword({ login: user.login, password, previousPassword }) - .then(this.handleSuccessfulChange) - .catch(this.handleFailedChange); + changePassword({ login: user.login, password, previousPassword }).then( + this.handleSuccessfulChange, + () => {} + ); } }; @@ -95,7 +98,7 @@ export default class Password extends Component { autoComplete="off" id="old_password" name="old_password" - ref={elem => (this.oldPassword = elem)} + ref={elem => (this.oldPassword = elem!)} required={true} type="password" /> @@ -109,7 +112,7 @@ export default class Password extends Component { autoComplete="off" id="password" name="password" - ref={elem => (this.password = elem)} + ref={elem => (this.password = elem!)} required={true} type="password" /> @@ -123,7 +126,7 @@ export default class Password extends Component { autoComplete="off" id="password_confirmation" name="password_confirmation" - ref={elem => (this.passwordConfirmation = elem)} + ref={elem => (this.passwordConfirmation = elem!)} required={true} type="password" /> diff --git a/server/sonar-web/src/main/js/apps/account/components/Security.js b/server/sonar-web/src/main/js/apps/account/components/Security.tsx index af77c5eff8d..bca85261333 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Security.js +++ b/server/sonar-web/src/main/js/apps/account/components/Security.tsx @@ -17,17 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; import Password from './Password'; import Tokens from './Tokens'; import { translate } from '../../../helpers/l10n'; -import { getCurrentUser } from '../../../store/rootReducer'; +import { getCurrentUser, Store } from '../../../store/rootReducer'; +import { LoggedInUser } from '../../../app/types'; -function Security(props) { - const { user } = props; +interface Props { + user: LoggedInUser; +} +function Security({ user }: Props) { return ( <div className="account-body account-container"> <Helmet title={translate('my_account.security')} /> @@ -37,4 +40,8 @@ function Security(props) { ); } -export default connect(state => ({ user: getCurrentUser(state) }))(Security); +const mapStateToProps = (state: Store) => ({ + user: getCurrentUser(state) as LoggedInUser +}); + +export default connect(mapStateToProps)(Security); diff --git a/server/sonar-web/src/main/js/apps/account/components/UserCard.js b/server/sonar-web/src/main/js/apps/account/components/UserCard.tsx index a67a2ef2eb0..6afeeaaf967 100644 --- a/server/sonar-web/src/main/js/apps/account/components/UserCard.js +++ b/server/sonar-web/src/main/js/apps/account/components/UserCard.tsx @@ -17,27 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import Avatar from '../../../components/ui/Avatar'; +import { LoggedInUser } from '../../../app/types'; -export default class UserCard extends React.PureComponent { - static propTypes = { - user: PropTypes.object.isRequired - }; - - render() { - const { user } = this.props; +interface Props { + user: LoggedInUser; +} - return ( - <div className="account-user"> - <div className="pull-left account-user-avatar" id="avatar"> - <Avatar hash={user.avatar} name={user.name} size={60} /> - </div> - <h1 className="pull-left" id="name"> - {user.name} - </h1> +export default function UserCard({ user }: Props) { + return ( + <div className="account-user"> + <div className="pull-left account-user-avatar" id="avatar"> + <Avatar hash={user.avatar} name={user.name} size={60} /> </div> - ); - } + <h1 className="pull-left" id="name"> + {user.name} + </h1> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/account/profile/Profile.js b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx index 3b91c7649dc..f03d64cac11 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/Profile.js +++ b/server/sonar-web/src/main/js/apps/account/profile/Profile.tsx @@ -17,32 +17,21 @@ * 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 UserExternalIdentity from './UserExternalIdentity'; import UserGroups from './UserGroups'; import UserScmAccounts from './UserScmAccounts'; -import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer'; +import { getCurrentUser, areThereCustomOrganizations, Store } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; +import { LoggedInUser } from '../../../app/types'; -/*:: -type Props = { - customOrganizations: boolean, - user: { - email?: string, - externalProvider?: string, - groups: Array<*>, - local: boolean, - login: string, - scmAccounts: Array<*> - } -}; -*/ - -function Profile(props /*: Props */) { - const { customOrganizations, user } = props; +interface Props { + customOrganizations?: boolean; + user: LoggedInUser; +} +function Profile({ customOrganizations, user }: Props) { return ( <div className="account-body account-container"> <div className="boxed-group boxed-group-inner"> @@ -63,8 +52,12 @@ function Profile(props /*: Props */) { </div> )} - {!customOrganizations && <hr className="account-separator" />} - {!customOrganizations && <UserGroups groups={user.groups} />} + {!customOrganizations && ( + <> + <hr className="account-separator" /> + <UserGroups groups={user.groups} /> + </> + )} <hr /> @@ -74,9 +67,9 @@ function Profile(props /*: Props */) { ); } -const mapStateToProps = state => ({ +const mapStateToProps = (state: Store) => ({ customOrganizations: areThereCustomOrganizations(state), - user: getCurrentUser(state) + user: getCurrentUser(state) as LoggedInUser }); export default connect(mapStateToProps)(Profile); diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx index 8e0536fe3ac..a71dad75ff4 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js +++ b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx @@ -17,13 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { getIdentityProviders } from '../../../api/users'; import * as theme from '../../../app/theme'; import { getTextColor } from '../../../helpers/colors'; +import { LoggedInUser, IdentityProvider } from '../../../app/types'; +import { getBaseUrl } from '../../../helpers/urls'; -export default class UserExternalIdentity extends React.PureComponent { - state = { +interface Props { + user: LoggedInUser; +} + +interface State { + identityProvider?: IdentityProvider; + loading: boolean; +} + +export default class UserExternalIdentity extends React.PureComponent<Props, State> { + mounted = false; + state: State = { loading: true }; @@ -32,7 +44,7 @@ export default class UserExternalIdentity extends React.PureComponent { this.fetchIdentityProviders(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.user !== this.props.user) { this.fetchIdentityProviders(); } @@ -90,7 +102,7 @@ export default class UserExternalIdentity extends React.PureComponent { alt={identityProvider.name} className="little-spacer-right" height="14" - src={window.baseUrl + identityProvider.iconPath} + src={getBaseUrl() + identityProvider.iconPath} width="14" />{' '} {user.externalIdentity} diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserGroups.js b/server/sonar-web/src/main/js/apps/account/profile/UserGroups.tsx index f81ba233119..a8a5e235a34 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/UserGroups.js +++ b/server/sonar-web/src/main/js/apps/account/profile/UserGroups.tsx @@ -17,29 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { translate } from '../../../helpers/l10n'; -export default class UserGroups extends React.PureComponent { - static propTypes = { - groups: PropTypes.arrayOf(PropTypes.string).isRequired - }; - - render() { - const { groups } = this.props; +interface Props { + groups: string[]; +} - return ( - <div> - <h2 className="spacer-bottom">{translate('my_profile.groups')}</h2> - <ul id="groups"> - {groups.map(group => ( - <li className="little-spacer-bottom" key={group} title={group}> - {group} - </li> - ))} - </ul> - </div> - ); - } +export default function UserGroups({ groups }: Props) { + return ( + <div> + <h2 className="spacer-bottom">{translate('my_profile.groups')}</h2> + <ul id="groups"> + {groups.map(group => ( + <li className="little-spacer-bottom" key={group} title={group}> + {group} + </li> + ))} + </ul> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserScmAccounts.js b/server/sonar-web/src/main/js/apps/account/profile/UserScmAccounts.tsx index a306959eade..13be6a89f97 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/UserScmAccounts.js +++ b/server/sonar-web/src/main/js/apps/account/profile/UserScmAccounts.tsx @@ -17,40 +17,36 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { translate } from '../../../helpers/l10n'; +import { LoggedInUser } from '../../../app/types'; -export default class UserScmAccounts extends React.PureComponent { - static propTypes = { - user: PropTypes.object.isRequired, - scmAccounts: PropTypes.arrayOf(PropTypes.string).isRequired - }; +interface Props { + scmAccounts: string[]; + user: LoggedInUser; +} - render() { - const { user, scmAccounts } = this.props; +export default function UserScmAccounts({ user, scmAccounts }: Props) { + return ( + <div> + <h2 className="spacer-bottom">{translate('my_profile.scm_accounts')}</h2> + <ul id="scm-accounts"> + <li className="little-spacer-bottom text-ellipsis" title={user.login}> + {user.login} + </li> - return ( - <div> - <h2 className="spacer-bottom">{translate('my_profile.scm_accounts')}</h2> - <ul id="scm-accounts"> - <li className="little-spacer-bottom text-ellipsis" title={user.login}> - {user.login} + {user.email && ( + <li className="little-spacer-bottom text-ellipsis" title={user.email}> + {user.email} </li> + )} - {user.email && ( - <li className="little-spacer-bottom text-ellipsis" title={user.email}> - {user.email} - </li> - )} - - {scmAccounts.map(scmAccount => ( - <li className="little-spacer-bottom" key={scmAccount} title={scmAccount}> - {scmAccount} - </li> - ))} - </ul> - </div> - ); - } + {scmAccounts.map(scmAccount => ( + <li className="little-spacer-bottom" key={scmAccount} title={scmAccount}> + {scmAccount} + </li> + ))} + </ul> + </div> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx index c4dd1380821..d20c0fd9ac8 100644 --- a/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/CreateProjectPage-test.tsx @@ -40,9 +40,11 @@ jest.mock('../../../../api/users', () => ({ const user: LoggedInUser = { externalProvider: 'github', + groups: [], isLoggedIn: true, login: 'foo', - name: 'Foo' + name: 'Foo', + scmAccounts: [] }; beforeEach(() => { diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx index f67db69e464..b2051ab3f53 100644 --- a/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/ManualProjectCreate-test.tsx @@ -69,7 +69,7 @@ it('should correctly create a project', async () => { function getWrapper(props = {}) { return shallow( <ManualProjectCreate - currentUser={{ isLoggedIn: true, login: 'foo', name: 'Foo' }} + currentUser={{ groups: [], isLoggedIn: true, login: 'foo', name: 'Foo', scmAccounts: [] }} fetchMyOrganizations={jest.fn()} onProjectCreate={jest.fn()} userOrganizations={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]} diff --git a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap index fffc678a6db..bafced94b3e 100644 --- a/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/create/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap @@ -116,9 +116,11 @@ exports[`should render with Manual creation only 1`] = ` currentUser={ Object { "externalProvider": "microsoft", + "groups": Array [], "isLoggedIn": true, "login": "foo", "name": "Foo", + "scmAccounts": Array [], } } onProjectCreate={[Function]} diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx index 39f0233081f..98e26a55df0 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorial-test.tsx @@ -33,9 +33,11 @@ const component = { }; const loggedInUser: LoggedInUser = { + groups: [], isLoggedIn: true, login: 'luke', - name: 'Skywalker' + name: 'Skywalker', + scmAccounts: [] }; it('renders correctly', () => { diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap index af09b977a36..b7838005879 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/__snapshots__/AnalyzeTutorial-test.tsx.snap @@ -21,9 +21,11 @@ exports[`renders correctly 1`] = ` <TokenStep currentUser={ Object { + "groups": Array [], "isLoggedIn": true, "login": "luke", "name": "Skywalker", + "scmAccounts": Array [], } } finished={false} |