+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-
-export default function AccountDeleted() {
- return (
- <div className="page-wrapper-simple display-flex-column">
- <Alert className="huge-spacer-bottom" variant="success">
- {translate('my_profile.delete_account.success')}
- </Alert>
-
- <div className="page-simple text-center">
- <p className="big-spacer-bottom">
- <h1>{translate('my_profile.delete_account.feedback.reason.explanation')}</h1>
- </p>
- <p className="spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('my_profile.delete_account.feedback.call_to_action')}
- id="my_profile.delete_account.feedback.call_to_action"
- values={{
- link: <Link to="/about/contact">{translate('footer.contact_us')}</Link>
- }}
- />
- </p>
- <p>
- <Link to="/">{translate('go_back_to_homepage')}</Link>
- </p>
- </div>
- </div>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import AccountDeleted from '../AccountDeleted';
-
-it('should render correctly', () => {
- expect(shallow(<AccountDeleted />)).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="page-wrapper-simple display-flex-column"
->
- <Alert
- className="huge-spacer-bottom"
- variant="success"
- >
- my_profile.delete_account.success
- </Alert>
- <div
- className="page-simple text-center"
- >
- <p
- className="big-spacer-bottom"
- >
- <h1>
- my_profile.delete_account.feedback.reason.explanation
- </h1>
- </p>
- <p
- className="spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="my_profile.delete_account.feedback.call_to_action"
- id="my_profile.delete_account.feedback.call_to_action"
- values={
- Object {
- "link": <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/about/contact"
- >
- footer.contact_us
- </Link>,
- }
- }
- />
- </p>
- <p>
- <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/"
- >
- go_back_to_homepage
- </Link>
- </p>
- </div>
-</div>
-`;
import('../../apps/feedback/downgrade/DowngradeFeedback')
)}
/>
- <Route
- path="account-deleted"
- component={lazyLoad(() => import('../components/AccountDeleted'))}
- />
</>
)}
<RouteWithChildRoutes path="organizations" childRoutes={organizationsRoutes} />
padding: 40px 0;
}
-.account-separator {
- height: 0;
- margin: 40px 0;
+.account-profile .boxed-group-inner:not(:first-child) {
border-top: 1px solid var(--barBorderColor);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
-import { isSonarCloud } from '../../../helpers/system';
-import UserDeleteAccount from './UserDeleteAccount';
import UserExternalIdentity from './UserExternalIdentity';
-import UserGroups from './UserGroups';
-import UserScmAccounts from './UserScmAccounts';
-export interface Props {
+export interface ProfileProps {
currentUser: T.LoggedInUser;
}
-export function Profile({ currentUser }: Props) {
+export function Profile({ currentUser }: ProfileProps) {
+ const isExternalProvider = !currentUser.local && currentUser.externalProvider !== 'sonarqube';
+
return (
- <div className="account-body account-container">
- <div className="boxed-group boxed-group-inner">
- <div className="spacer-bottom">
- {translate('login')}: <strong id="login">{currentUser.login}</strong>
- </div>
+ <div className="account-body account-container account-profile">
+ <div className="boxed-group">
+ {renderLogin()}
+ {renderEmail()}
+ {renderUserGroups()}
+ {renderScmAccounts()}
+ </div>
+ </div>
+ );
- {!currentUser.local && currentUser.externalProvider !== 'sonarqube' && (
- <div className="spacer-bottom" id="identity-provider">
- <UserExternalIdentity user={currentUser} />
- </div>
- )}
+ function renderLogin() {
+ if (!currentUser.login && !isExternalProvider) {
+ return null;
+ }
- {Boolean(currentUser.email) && (
- <div className="spacer-bottom">
- {translate('my_profile.email')}: <strong id="email">{currentUser.email}</strong>
- </div>
+ return (
+ <div className="boxed-group-inner">
+ <h2 className="spacer-bottom">{translate('my_profile.login')}</h2>
+ {currentUser.login && (
+ <p className="spacer-bottom" id="login">
+ {currentUser.login}
+ </p>
)}
+ {isExternalProvider && <UserExternalIdentity user={currentUser} />}
+ </div>
+ );
+ }
- {!isSonarCloud() && (
- <>
- <hr className="account-separator" />
- <UserGroups groups={currentUser.groups} />
- </>
- )}
+ function renderEmail() {
+ if (!currentUser.email) {
+ return null;
+ }
- <hr />
+ return (
+ <div className="boxed-group-inner">
+ <h2 className="spacer-bottom">{translate('my_profile.email')}</h2>
+ <div className="spacer-bottom">
+ <p id="email">{currentUser.email}</p>
+ </div>
+ </div>
+ );
+ }
- <UserScmAccounts scmAccounts={currentUser.scmAccounts} user={currentUser} />
+ function renderUserGroups() {
+ if (!currentUser.groups || currentUser.groups.length === 0) {
+ return null;
+ }
- {isSonarCloud() && (
- <>
- <hr />
+ return (
+ <div className="boxed-group-inner">
+ <h2 className="spacer-bottom">{translate('my_profile.groups')}</h2>
+ <ul id="groups">
+ {currentUser.groups.map(group => (
+ <li className="little-spacer-bottom" key={group} title={group}>
+ {group}
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
- <UserDeleteAccount user={currentUser} />
- </>
- )}
+ function renderScmAccounts() {
+ if (
+ !currentUser.login &&
+ !currentUser.email &&
+ (!currentUser.scmAccounts || currentUser.scmAccounts.length === 0)
+ ) {
+ return null;
+ }
+
+ return (
+ <div className="boxed-group-inner">
+ <h2 className="spacer-bottom">
+ {translate('my_profile.scm_accounts')}
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={translate('my_profile.scm_accounts.tooltip')}
+ />
+ </h2>
+ <ul id="scm-accounts">
+ {currentUser.login && (
+ <li className="little-spacer-bottom text-ellipsis" title={currentUser.login}>
+ {currentUser.login}
+ </li>
+ )}
+
+ {currentUser.email && (
+ <li className="little-spacer-bottom text-ellipsis" title={currentUser.email}>
+ {currentUser.email}
+ </li>
+ )}
+
+ {currentUser.scmAccounts &&
+ currentUser.scmAccounts.length > 0 &&
+ currentUser.scmAccounts.map(scmAccount => (
+ <li className="little-spacer-bottom" key={scmAccount} title={scmAccount}>
+ {scmAccount}
+ </li>
+ ))}
+ </ul>
</div>
- </div>
- );
+ );
+ }
}
export default whenLoggedIn(Profile);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { Button } from 'sonar-ui-common/components/controls/buttons';
-import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getOrganizationsThatPreventDeletion } from '../../../api/organizations';
-import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
-import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations';
-import UserDeleteAccountContent from './UserDeleteAccountContent';
-import UserDeleteAccountModal from './UserDeleteAccountModal';
-
-interface Props {
- user: T.LoggedInUser;
- userOrganizations: T.Organization[];
-}
-
-interface State {
- loading: boolean;
- organizationsToTransferOrDelete: T.Organization[];
- showModal: boolean;
-}
-
-export class UserDeleteAccount extends React.PureComponent<Props, State> {
- mounted = false;
-
- state: State = {
- loading: true,
- organizationsToTransferOrDelete: [],
- showModal: false
- };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchOrganizationsThatPreventDeletion();
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchOrganizationsThatPreventDeletion = () => {
- getOrganizationsThatPreventDeletion().then(
- ({ organizations }) => {
- if (this.mounted) {
- this.setState({
- loading: false,
- organizationsToTransferOrDelete: organizations
- });
- }
- },
- () => {}
- );
- };
-
- toggleModal = () => {
- if (this.mounted) {
- this.setState(state => ({
- showModal: !state.showModal
- }));
- }
- };
-
- render() {
- const { user, userOrganizations } = this.props;
- const { organizationsToTransferOrDelete, loading, showModal } = this.state;
-
- const label = translate('my_profile.delete_account');
-
- return (
- <div>
- <h2 className="spacer-bottom">{label}</h2>
-
- <DeferredSpinner loading={loading} />
-
- {!loading && (
- <>
- <UserDeleteAccountContent
- className="list-styled no-padding big-spacer-top big-spacer-bottom"
- organizationsSafeToDelete={userOrganizations}
- organizationsToTransferOrDelete={organizationsToTransferOrDelete}
- />
-
- <Button
- className="button-red"
- disabled={organizationsToTransferOrDelete.length > 0}
- onClick={this.toggleModal}
- type="button">
- {translate('delete')}
- </Button>
-
- {showModal && (
- <UserDeleteAccountModal
- label={label}
- organizationsSafeToDelete={userOrganizations}
- organizationsToTransferOrDelete={organizationsToTransferOrDelete}
- toggleModal={this.toggleModal}
- user={user}
- />
- )}
- </>
- )}
- </div>
- );
- }
-}
-
-export default whenLoggedIn(withUserOrganizations(UserDeleteAccount));
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getOrganizationUrl } from '../../../helpers/urls';
-
-function getOrganizationLink(org: T.Organization, i: number, organizations: T.Organization[]) {
- return (
- <span key={org.key}>
- <Link to={getOrganizationUrl(org.key)}>{org.name}</Link>
- {i < organizations.length - 1 && ', '}
- </span>
- );
-}
-
-export function ShowOrganizationsToTransferOrDelete({
- organizations
-}: {
- organizations: T.Organization[];
-}) {
- return (
- <>
- <p className="big-spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('my_profile.delete_account.info.orgs_to_transfer_or_delete')}
- id="my_profile.delete_account.info.orgs_to_transfer_or_delete"
- values={{
- organizations: <>{organizations.map(getOrganizationLink)}</>
- }}
- />
- </p>
-
- <Alert className="big-spacer-bottom" variant="warning">
- <FormattedMessage
- defaultMessage={translate(
- 'my_profile.delete_account.info.orgs_to_transfer_or_delete.info'
- )}
- id="my_profile.delete_account.info.orgs_to_transfer_or_delete.info"
- values={{
- link: (
- <a
- href="https://sieg.eu.ngrok.io/documentation/organizations/overview/#how-to-transfer-ownership-of-an-organization"
- rel="noopener noreferrer"
- target="_blank">
- {translate('my_profile.delete_account.info.orgs_to_transfer_or_delete.info.link')}
- </a>
- )
- }}
- />
- </Alert>
- </>
- );
-}
-
-export function ShowOrganizations({
- className,
- organizations
-}: {
- className?: string;
- organizations: T.Organization[];
-}) {
- const organizationsIAdministrate = organizations.filter(o => o.actions && o.actions.admin);
-
- return (
- <ul className={className}>
- <li className="spacer-bottom">{translate('my_profile.delete_account.info')}</li>
-
- <li className="spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('my_profile.delete_account.data.info')}
- id="my_profile.delete_account.data.info"
- values={{
- help: (
- <a
- href="/documentation/user-guide/user-account/#delete-your-user-account"
- rel="noopener noreferrer"
- target="_blank">
- {translate('learn_more')}
- </a>
- )
- }}
- />
- </li>
-
- {organizations.length > 0 && (
- <li className="spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('my_profile.delete_account.info.orgs.members')}
- id="my_profile.delete_account.info.orgs.members"
- values={{
- organizations: <>{organizations.map(getOrganizationLink)}</>
- }}
- />
- </li>
- )}
-
- {organizationsIAdministrate.length > 0 && (
- <li className="spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('my_profile.delete_account.info.orgs.administrators')}
- id="my_profile.delete_account.info.orgs.administrators"
- values={{
- organizations: <>{organizationsIAdministrate.map(getOrganizationLink)}</>
- }}
- />
- </li>
- )}
- </ul>
- );
-}
-
-interface UserDeleteAccountContentProps {
- className?: string;
- organizationsSafeToDelete: T.Organization[];
- organizationsToTransferOrDelete: T.Organization[];
-}
-
-export default function UserDeleteAccountContent({
- className,
- organizationsSafeToDelete,
- organizationsToTransferOrDelete
-}: UserDeleteAccountContentProps) {
- if (organizationsToTransferOrDelete.length > 0) {
- return <ShowOrganizationsToTransferOrDelete organizations={organizationsToTransferOrDelete} />;
- }
-
- return <ShowOrganizations className={className} organizations={organizationsSafeToDelete} />;
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { connect } from 'react-redux';
-import InputValidationField from 'sonar-ui-common/components/controls/InputValidationField';
-import ValidationModal from 'sonar-ui-common/components/controls/ValidationModal';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
-import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import { deactivateUser } from '../../../api/users';
-import RecentHistory from '../../../app/components/RecentHistory';
-import { Router, withRouter } from '../../../components/hoc/withRouter';
-import { doLogout } from '../../../store/rootActions';
-import UserDeleteAccountContent from './UserDeleteAccountContent';
-
-interface Values {
- login: string;
-}
-
-interface DeleteModalProps {
- doLogout: () => Promise<void>;
- label: string;
- organizationsSafeToDelete: T.Organization[];
- organizationsToTransferOrDelete: T.Organization[];
- router: Pick<Router, 'push'>;
- toggleModal: VoidFunction;
- user: T.LoggedInUser;
-}
-
-export class UserDeleteAccountModal extends React.PureComponent<DeleteModalProps> {
- handleSubmit = () => {
- const { user } = this.props;
-
- return deactivateUser({ login: user.login })
- .then(this.props.doLogout)
- .then(() => {
- RecentHistory.clear();
- window.location.replace('/account-deleted');
- });
- };
-
- handleValidate = ({ login }: Values) => {
- const { user } = this.props;
- const errors: { login?: string } = {};
- const trimmedLogin = login.trim();
-
- if (!trimmedLogin) {
- errors.login = translate('my_profile.delete_account.login.required');
- } else if (user.externalIdentity && trimmedLogin !== user.externalIdentity.trim()) {
- errors.login = translate('my_profile.delete_account.login.wrong_value');
- }
-
- return errors;
- };
-
- render() {
- const {
- label,
- organizationsSafeToDelete,
- organizationsToTransferOrDelete,
- toggleModal,
- user
- } = this.props;
-
- return (
- <ValidationModal
- confirmButtonText={translate('delete')}
- header={translateWithParameters(
- 'my_profile.delete_account.modal.header',
- label,
- user.externalIdentity || ''
- )}
- initialValues={{
- login: ''
- }}
- isDestructive={true}
- onClose={toggleModal}
- onSubmit={this.handleSubmit}
- validate={this.handleValidate}>
- {({ dirty, errors, handleBlur, handleChange, isSubmitting, touched, values }) => (
- <>
- <Alert className="big-spacer-bottom" variant="error">
- {translate('my_profile.warning_message')}
- </Alert>
-
- <UserDeleteAccountContent
- className="list-styled no-padding big-spacer-bottom"
- organizationsSafeToDelete={organizationsSafeToDelete}
- organizationsToTransferOrDelete={organizationsToTransferOrDelete}
- />
-
- <InputValidationField
- autoFocus={true}
- dirty={dirty}
- disabled={isSubmitting}
- error={errors.login}
- id="user-login"
- label={
- <label htmlFor="user-login">
- {translate('my_profile.delete_account.verify')}
- <em className="mandatory">*</em>
- </label>
- }
- name="login"
- onBlur={handleBlur}
- onChange={handleChange}
- touched={touched.login}
- type="text"
- value={values.login}
- />
- </>
- )}
- </ValidationModal>
- );
- }
-}
-
-const mapStateToProps = () => ({});
-
-const mapDispatchToProps = { doLogout: doLogout as any };
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(withRouter(UserDeleteAccountModal));
import { getIdentityProviders } from '../../../api/users';
import { colors } from '../../../app/theme';
-interface Props {
+export interface UserExternalIdentityProps {
user: T.LoggedInUser;
}
loading: boolean;
}
-export default class UserExternalIdentity extends React.PureComponent<Props, State> {
+export default class UserExternalIdentity extends React.PureComponent<
+ UserExternalIdentityProps,
+ State
+> {
mounted = false;
state: State = {
loading: true
this.fetchIdentityProviders();
}
- componentDidUpdate(prevProps: Props) {
+ componentDidUpdate(prevProps: UserExternalIdentityProps) {
if (prevProps.user !== this.props.user) {
this.fetchIdentityProviders();
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { translate } from 'sonar-ui-common/helpers/l10n';
-
-interface Props {
- groups: string[];
-}
-
-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>
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-
-interface Props {
- scmAccounts: string[];
- user: T.LoggedInUser;
-}
-
-export default function UserScmAccounts({ user, scmAccounts }: Props) {
- return (
- <div>
- <h2 className="spacer-bottom">
- {translate('my_profile.scm_accounts')}
- <HelpTooltip
- className="little-spacer-left"
- overlay={translate('my_profile.scm_accounts.tooltip')}
- />
- </h2>
- <ul id="scm-accounts">
- <li className="little-spacer-bottom text-ellipsis" title={user.login}>
- {user.login}
- </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>
- );
-}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { isSonarCloud } from '../../../../helpers/system';
import { mockLoggedInUser } from '../../../../helpers/testMocks';
-import { Profile, Props } from '../Profile';
+import { Profile, ProfileProps } from '../Profile';
-jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn().mockReturnValue(false) }));
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render email', () => {
- expect(
- shallowRender(mockLoggedInUser({ email: 'john@doe.com' }))
- .find('#email')
- .exists()
- ).toBe(true);
-});
-
-it('should render external identity', () => {
- expect(
- shallowRender(mockLoggedInUser({ local: false, externalProvider: 'github' }))
- .find('UserExternalIdentity')
- .exists()
- ).toBe(true);
+it('should render correctly a local user', () => {
+ expect(shallowRender({ local: true, externalProvider: 'sonarqube' })).toMatchSnapshot();
});
-it('should not display user groups', () => {
- (isSonarCloud as jest.Mock).mockReturnValueOnce(true);
+it('should render correctly a IDP user', () => {
expect(
- shallowRender()
- .find('UserGroups')
- .exists()
- ).toBe(false);
+ shallowRender({
+ local: false,
+ externalProvider: 'github',
+ email: undefined,
+ login: undefined,
+ scmAccounts: []
+ })
+ ).toMatchSnapshot();
});
-function shallowRender(currentUser: Props['currentUser'] = mockLoggedInUser()) {
- return shallow(<Profile currentUser={currentUser} />);
+function shallowRender(userOverrides?: Partial<ProfileProps['currentUser']>) {
+ return shallow(
+ <Profile
+ currentUser={{
+ ...mockLoggedInUser({
+ email: 'john@doe.com',
+ groups: ['G1', 'G2'],
+ scmAccounts: ['SCM1', 'SCM2'],
+ ...userOverrides
+ })
+ }}
+ />
+ );
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { getOrganizationsThatPreventDeletion } from '../../../../api/organizations';
-import { mockLoggedInUser, mockOrganization } from '../../../../helpers/testMocks';
-import { UserDeleteAccount } from '../UserDeleteAccount';
-
-jest.mock('../../../../api/organizations', () => ({
- getOrganizationsThatPreventDeletion: jest.fn().mockResolvedValue({ organizations: [] })
-}));
-
-beforeEach(() => {
- jest.clearAllMocks();
-});
-
-const organizationToTransferOrDelete = {
- key: 'luke-leia',
- name: 'Luke and Leia'
-};
-
-it('should render correctly', async () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-
- click(wrapper.find('Button'));
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should get some organizations', async () => {
- (getOrganizationsThatPreventDeletion as jest.Mock).mockResolvedValue({
- organizations: [organizationToTransferOrDelete]
- });
-
- const wrapper = shallowRender();
-
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state('loading')).toBeFalsy();
- expect(wrapper.state('organizationsToTransferOrDelete')).toEqual([
- organizationToTransferOrDelete
- ]);
- expect(getOrganizationsThatPreventDeletion).toBeCalled();
- expect(wrapper.find('Button').prop('disabled')).toBe(true);
-});
-
-it('should toggle modal', () => {
- const wrapper = shallowRender();
- wrapper.setState({ loading: false });
- expect(wrapper.find('Connect(withRouter(UserDeleteAccountModal))').exists()).toBe(false);
- click(wrapper.find('Button'));
- expect(wrapper.find('Connect(withRouter(UserDeleteAccountModal))').exists()).toBe(true);
-});
-
-function shallowRender(props: Partial<UserDeleteAccount['props']> = {}) {
- const user = mockLoggedInUser({ externalIdentity: 'luke' });
-
- const userOrganizations = [
- mockOrganization({ key: 'luke-leia', name: 'Luke and Leia' }),
- mockOrganization({ key: 'luke', name: 'Luke Skywalker' })
- ];
-
- return shallow<UserDeleteAccount>(
- <UserDeleteAccount user={user} userOrganizations={userOrganizations} {...props} />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import UserDeleteAccountContent, {
- ShowOrganizations,
- ShowOrganizationsToTransferOrDelete
-} from '../UserDeleteAccountContent';
-
-const organizationSafeToDelete = {
- key: 'luke',
- name: 'Luke Skywalker'
-};
-
-const organizationToTransferOrDelete = {
- key: 'luke-leia',
- name: 'Luke and Leia'
-};
-
-it('should render content correctly', () => {
- expect(
- shallow(
- <UserDeleteAccountContent
- className="my-class"
- organizationsSafeToDelete={[organizationSafeToDelete]}
- organizationsToTransferOrDelete={[organizationToTransferOrDelete]}
- />
- )
- ).toMatchSnapshot();
-
- expect(
- shallow(
- <UserDeleteAccountContent
- className="my-class"
- organizationsSafeToDelete={[organizationSafeToDelete]}
- organizationsToTransferOrDelete={[]}
- />
- )
- ).toMatchSnapshot();
-});
-
-it('should render correctly ShowOrganizationsToTransferOrDelete', () => {
- expect(
- shallow(
- <ShowOrganizationsToTransferOrDelete organizations={[organizationToTransferOrDelete]} />
- )
- ).toMatchSnapshot();
-});
-
-it('should render correctly ShowOrganizations', () => {
- expect(
- shallow(<ShowOrganizations organizations={[organizationSafeToDelete]} />)
- ).toMatchSnapshot();
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { deactivateUser } from '../../../../api/users';
-import { mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
-import { UserDeleteAccountModal } from '../UserDeleteAccountModal';
-
-jest.mock('../../../../api/users', () => ({
- deactivateUser: jest.fn()
-}));
-
-const organizationSafeToDelete = {
- key: 'luke',
- name: 'Luke Skywalker'
-};
-
-const organizationToTransferOrDelete = {
- key: 'luke-leia',
- name: 'Luke and Leia'
-};
-
-it('should render modal correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should handle submit', async () => {
- (deactivateUser as jest.Mock).mockResolvedValue(true);
- window.location.replace = jest.fn();
-
- const wrapper = shallowRender();
- const instance = wrapper.instance();
-
- instance.handleSubmit();
- await waitAndUpdate(wrapper);
-
- expect(deactivateUser).toBeCalled();
- expect(window.location.replace).toHaveBeenCalledWith('/account-deleted');
-});
-
-it('should validate user input', () => {
- const wrapper = shallowRender();
- const instance = wrapper.instance();
- const { handleValidate } = instance;
-
- expect(handleValidate({ login: '' }).login).toBe('my_profile.delete_account.login.required');
- expect(handleValidate({ login: 'abc' }).login).toBe(
- 'my_profile.delete_account.login.wrong_value'
- );
- expect(handleValidate({ login: 'luke' }).login).toBeUndefined();
-});
-
-function shallowRender(props: Partial<UserDeleteAccountModal['props']> = {}) {
- const user = mockLoggedInUser({ externalIdentity: 'luke' });
-
- return shallow<UserDeleteAccountModal>(
- <UserDeleteAccountModal
- doLogout={jest.fn().mockResolvedValue(true)}
- label="label"
- organizationsSafeToDelete={[organizationSafeToDelete]}
- organizationsToTransferOrDelete={[organizationToTransferOrDelete]}
- router={mockRouter()}
- toggleModal={jest.fn()}
- user={user}
- {...props}
- />
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { mockLoggedInUser } from '../../../../helpers/testMocks';
+import UserExternalIdentity, { UserExternalIdentityProps } from '../UserExternalIdentity';
+
+jest.mock('../../../../api/users', () => ({
+ getIdentityProviders: jest.fn().mockResolvedValue({
+ identityProviders: [
+ {
+ backgroundColor: '#444444',
+ iconPath: '/images/github.svg',
+ key: 'github',
+ name: 'GitHub'
+ }
+ ]
+ })
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render a fallback when idp is not listed', async () => {
+ const wrapper = shallowRender({ externalProvider: 'ggithub' });
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(userOverrides?: Partial<UserExternalIdentityProps['user']>) {
+ return shallow(
+ <UserExternalIdentity
+ user={{
+ ...mockLoggedInUser({
+ email: 'john@doe.com',
+ externalProvider: 'github',
+ local: false,
+ groups: ['G1', 'G2'],
+ scmAccounts: ['SCM1', 'SCM2'],
+ ...userOverrides
+ })
+ }}
+ />
+ );
+}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should render correctly a IDP user 1`] = `
<div
- className="account-body account-container"
+ className="account-body account-container account-profile"
>
<div
- className="boxed-group boxed-group-inner"
+ className="boxed-group"
>
<div
- className="spacer-bottom"
+ className="boxed-group-inner"
>
- login
- :
- <strong
- id="login"
+ <h2
+ className="spacer-bottom"
>
- luke
- </strong>
- </div>
- <div
- className="spacer-bottom"
- id="identity-provider"
- >
+ my_profile.login
+ </h2>
<UserExternalIdentity
user={
Object {
- "groups": Array [],
+ "email": undefined,
+ "externalProvider": "github",
+ "groups": Array [
+ "G1",
+ "G2",
+ ],
"isLoggedIn": true,
- "login": "luke",
+ "local": false,
+ "login": undefined,
"name": "Skywalker",
"scmAccounts": Array [],
}
}
/>
</div>
- <hr
- className="account-separator"
- />
- <UserGroups
- groups={Array []}
- />
- <hr />
- <UserScmAccounts
- scmAccounts={Array []}
- user={
- Object {
- "groups": Array [],
- "isLoggedIn": true,
- "login": "luke",
- "name": "Skywalker",
- "scmAccounts": Array [],
- }
- }
- />
+ <div
+ className="boxed-group-inner"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.groups
+ </h2>
+ <ul
+ id="groups"
+ >
+ <li
+ className="little-spacer-bottom"
+ key="G1"
+ title="G1"
+ >
+ G1
+ </li>
+ <li
+ className="little-spacer-bottom"
+ key="G2"
+ title="G2"
+ >
+ G2
+ </li>
+ </ul>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render correctly a local user 1`] = `
+<div
+ className="account-body account-container account-profile"
+>
+ <div
+ className="boxed-group"
+ >
+ <div
+ className="boxed-group-inner"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.login
+ </h2>
+ <p
+ className="spacer-bottom"
+ id="login"
+ >
+ luke
+ </p>
+ </div>
+ <div
+ className="boxed-group-inner"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.email
+ </h2>
+ <div
+ className="spacer-bottom"
+ >
+ <p
+ id="email"
+ >
+ john@doe.com
+ </p>
+ </div>
+ </div>
+ <div
+ className="boxed-group-inner"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.groups
+ </h2>
+ <ul
+ id="groups"
+ >
+ <li
+ className="little-spacer-bottom"
+ key="G1"
+ title="G1"
+ >
+ G1
+ </li>
+ <li
+ className="little-spacer-bottom"
+ key="G2"
+ title="G2"
+ >
+ G2
+ </li>
+ </ul>
+ </div>
+ <div
+ className="boxed-group-inner"
+ >
+ <h2
+ className="spacer-bottom"
+ >
+ my_profile.scm_accounts
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay="my_profile.scm_accounts.tooltip"
+ />
+ </h2>
+ <ul
+ id="scm-accounts"
+ >
+ <li
+ className="little-spacer-bottom text-ellipsis"
+ title="luke"
+ >
+ luke
+ </li>
+ <li
+ className="little-spacer-bottom text-ellipsis"
+ title="john@doe.com"
+ >
+ john@doe.com
+ </li>
+ <li
+ className="little-spacer-bottom"
+ key="SCM1"
+ title="SCM1"
+ >
+ SCM1
+ </li>
+ <li
+ className="little-spacer-bottom"
+ key="SCM2"
+ title="SCM2"
+ >
+ SCM2
+ </li>
+ </ul>
+ </div>
</div>
</div>
`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div>
- <h2
- className="spacer-bottom"
- >
- my_profile.delete_account
- </h2>
- <DeferredSpinner
- loading={true}
- timeout={100}
- />
-</div>
-`;
-
-exports[`should render correctly 2`] = `
-<div>
- <h2
- className="spacer-bottom"
- >
- my_profile.delete_account
- </h2>
- <DeferredSpinner
- loading={false}
- timeout={100}
- />
- <UserDeleteAccountContent
- className="list-styled no-padding big-spacer-top big-spacer-bottom"
- organizationsSafeToDelete={
- Array [
- Object {
- "key": "luke-leia",
- "name": "Luke and Leia",
- },
- Object {
- "key": "luke",
- "name": "Luke Skywalker",
- },
- ]
- }
- organizationsToTransferOrDelete={Array []}
- />
- <Button
- className="button-red"
- disabled={false}
- onClick={[Function]}
- type="button"
- >
- delete
- </Button>
-</div>
-`;
-
-exports[`should render correctly 3`] = `
-<div>
- <h2
- className="spacer-bottom"
- >
- my_profile.delete_account
- </h2>
- <DeferredSpinner
- loading={false}
- timeout={100}
- />
- <UserDeleteAccountContent
- className="list-styled no-padding big-spacer-top big-spacer-bottom"
- organizationsSafeToDelete={
- Array [
- Object {
- "key": "luke-leia",
- "name": "Luke and Leia",
- },
- Object {
- "key": "luke",
- "name": "Luke Skywalker",
- },
- ]
- }
- organizationsToTransferOrDelete={Array []}
- />
- <Button
- className="button-red"
- disabled={false}
- onClick={[Function]}
- type="button"
- >
- delete
- </Button>
- <Connect(withRouter(UserDeleteAccountModal))
- label="my_profile.delete_account"
- organizationsSafeToDelete={
- Array [
- Object {
- "key": "luke-leia",
- "name": "Luke and Leia",
- },
- Object {
- "key": "luke",
- "name": "Luke Skywalker",
- },
- ]
- }
- organizationsToTransferOrDelete={Array []}
- toggleModal={[Function]}
- user={
- Object {
- "externalIdentity": "luke",
- "groups": Array [],
- "isLoggedIn": true,
- "login": "luke",
- "name": "Skywalker",
- "scmAccounts": Array [],
- }
- }
- />
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render content correctly 1`] = `
-<ShowOrganizationsToTransferOrDelete
- organizations={
- Array [
- Object {
- "key": "luke-leia",
- "name": "Luke and Leia",
- },
- ]
- }
-/>
-`;
-
-exports[`should render content correctly 2`] = `
-<ShowOrganizations
- className="my-class"
- organizations={
- Array [
- Object {
- "key": "luke",
- "name": "Luke Skywalker",
- },
- ]
- }
-/>
-`;
-
-exports[`should render correctly ShowOrganizations 1`] = `
-<ul>
- <li
- className="spacer-bottom"
- >
- my_profile.delete_account.info
- </li>
- <li
- className="spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="my_profile.delete_account.data.info"
- id="my_profile.delete_account.data.info"
- values={
- Object {
- "help": <a
- href="/documentation/user-guide/user-account/#delete-your-user-account"
- rel="noopener noreferrer"
- target="_blank"
- >
- learn_more
- </a>,
- }
- }
- />
- </li>
- <li
- className="spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="my_profile.delete_account.info.orgs.members"
- id="my_profile.delete_account.info.orgs.members"
- values={
- Object {
- "organizations": <React.Fragment>
- <span>
- <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/luke"
- >
- Luke Skywalker
- </Link>
- </span>
- </React.Fragment>,
- }
- }
- />
- </li>
-</ul>
-`;
-
-exports[`should render correctly ShowOrganizationsToTransferOrDelete 1`] = `
-<Fragment>
- <p
- className="big-spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="my_profile.delete_account.info.orgs_to_transfer_or_delete"
- id="my_profile.delete_account.info.orgs_to_transfer_or_delete"
- values={
- Object {
- "organizations": <React.Fragment>
- <span>
- <Link
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/luke-leia"
- >
- Luke and Leia
- </Link>
- </span>
- </React.Fragment>,
- }
- }
- />
- </p>
- <Alert
- className="big-spacer-bottom"
- variant="warning"
- >
- <FormattedMessage
- defaultMessage="my_profile.delete_account.info.orgs_to_transfer_or_delete.info"
- id="my_profile.delete_account.info.orgs_to_transfer_or_delete.info"
- values={
- Object {
- "link": <a
- href="https://sieg.eu.ngrok.io/documentation/organizations/overview/#how-to-transfer-ownership-of-an-organization"
- rel="noopener noreferrer"
- target="_blank"
- >
- my_profile.delete_account.info.orgs_to_transfer_or_delete.info.link
- </a>,
- }
- }
- />
- </Alert>
-</Fragment>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render modal correctly 1`] = `
-<ValidationModal
- confirmButtonText="delete"
- header="my_profile.delete_account.modal.header.label.luke"
- initialValues={
- Object {
- "login": "",
- }
- }
- isDestructive={true}
- onClose={[MockFunction]}
- onSubmit={[Function]}
- validate={[Function]}
->
- <Component />
-</ValidationModal>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render a fallback when idp is not listed 1`] = `
+<div>
+ ggithub
+ :
+</div>
+`;
+
+exports[`should render correctly 1`] = `
+<div
+ className="identity-provider"
+ style={
+ Object {
+ "backgroundColor": "#444444",
+ "color": "#fff",
+ }
+ }
+>
+ <img
+ alt="GitHub"
+ className="little-spacer-right"
+ height="14"
+ src="/images/github.svg"
+ width="14"
+ />
+
+</div>
+`;
# MY PROFILE & MY ACCOUNT
#
#------------------------------------------------------------------------------
-my_profile.delete_account=Delete your user account
-my_profile.delete_account.success=Account successfully deleted
-my_profile.delete_account.feedback.reason.explanation=We are sorry to see you leave.
-my_profile.delete_account.feedback.call_to_action={link} to help improve our product or offer.
-my_profile.delete_account.info=We will immediately delete your account.
-my_profile.delete_account.data.info=All your data will be removed except your login. {help}
-my_profile.delete_account.info.orgs.members=You will be removed from the members of: {organizations}.
-my_profile.delete_account.info.orgs.administrators=You will be removed from the administrators of: {organizations}.
-my_profile.delete_account.info.orgs_to_transfer_or_delete=Your account is the only administrator for the following organization(s): {organizations}.
-my_profile.delete_account.info.orgs_to_transfer_or_delete.info=You must transfer administration permissions or delete these organizations before you can delete your SonarCloud account. {link}.
-my_profile.delete_account.info.orgs_to_transfer_or_delete.info.link=See Organization Admin Guide
-my_profile.delete_account.modal.header={0}: {1}
-my_profile.delete_account.login.required=Login is required
-my_profile.delete_account.login.wrong_value=Please type your login to confirm
-my_profile.delete_account.verify=To verify, please type your user account name below
+my_profile.login=Login
my_profile.email=Email
my_profile.groups=Groups
my_profile.scm_accounts=SCM Accounts
my_profile.sonarcloud_feature_notifications.description=Display a notification in the header when new features are deployed
my_profile.per_project_notifications.title=Notifications per project
my_profile.per_project_notifications.add=Add a project
-my_profile.warning_message=This is a definitive action. No account recovery will be possible.
my_account.page=My Account
my_account.notifications=Notifications