<!-- sonarcloud -->
## Prepare your organization
-A project must belong to an [organization](/organizations/overview/). Create one if you intend to collaborate with your team mates, or use your personal organization for test purposes.
+A project must belong to an [organization](/organizations/overview/). You need to create one to analyze a project and collaborate with your team mates.
[[info]]
-| ** Important note for private code:** Newly created organizations and personal organizations are under a free plan by default. This means projects analyzed on these organizations are public by default: the code will be browsable by anyone. If you want private projects, you should [upgrade your organization to a paid plan](/sonarcloud-pricing/).
+| ** Important note for private code:** Newly created organizations are under a free plan by default. This means projects analyzed on these organizations are public by default: the code will be browsable by anyone. If you want private projects, you should [upgrade your organization to a paid plan](/sonarcloud-pricing/).
Find the key of your organization, you will need it at later stages. It can be found on the top right corner of your organization's header.
* [Members](/organizations/manage-team/), who can have different permissions on the projects
* [Quality Profiles](/instance-administration/quality-profiles/) and [Quality Gates](/user-guide/quality-gates/), which can be customized and shared accross projects
-There are 2 kind of organizations:
-* **Personal organizations**. Each account has a personal organization linked to it. This is typically where open-source developers host their personal projects. It is not possible to delete this kind of organization.
-* **Standard organization**. This is the kind of organization that users want to create for their companies or for their open-source communities. As soon as you want to collaborate, it is a good idea to create such an organization.
-
Organizations can be on:
* **Free plan**. This is the default plan. Every project in an organization on the free plan is public.
* **Paid plan**. This plan unlocks the ability to have private projects. Go to the "Billing" page of your organization to upgrade it to the paid plan.
export interface AlmOrganization extends OrganizationBase {
almUrl: string;
key: string;
- personal: boolean;
privateRepos: number;
publicRepos: number;
}
local?: boolean;
login: string;
name: string;
- personalOrganization?: string;
scmAccounts: string[];
settings?: CurrentUserSetting[];
}
alm?: { key: string; membersSync: boolean; url: string };
adminPages?: Extension[];
canUpdateProjectsVisibilityToPrivate?: boolean;
- guarded?: boolean;
isDefault?: boolean;
key: string;
pages?: Extension[];
+++ /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 OrganizationDetailsForm from './OrganizationDetailsForm';
-import OrganizationDetailsStep from './OrganizationDetailsStep';
-import PlanStep from './PlanStep';
-import { Step } from './utils';
-import { ClearButton } from '../../../components/ui/buttons';
-import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
-import { getBaseUrl } from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-import { sanitizeAlmId } from '../../../helpers/almIntegrations';
-
-interface Props {
- almApplication: T.AlmApplication;
- almInstallId?: string;
- almOrganization: T.AlmOrganization;
- handleCancelImport: () => void;
- handleOrgDetailsFinish: (organization: T.Organization) => Promise<void>;
- handleOrgDetailsStepOpen: () => void;
- importPersonalOrg: T.Organization;
- onDone: () => void;
- organization?: T.Organization;
- step: Step;
- subscriptionPlans?: T.SubscriptionPlan[];
- updateOrganization: (
- organization: T.Organization & { installationId?: string }
- ) => Promise<string>;
-}
-
-export default class AutoPersonalOrganizationBind extends React.PureComponent<Props> {
- handleCreateOrganization = () => {
- const { organization } = this.props;
- if (!organization) {
- return Promise.reject();
- }
- return this.props.updateOrganization({
- ...organization,
- installationId: this.props.almInstallId
- });
- };
-
- handleOrgDetailsFinish = (organization: T.Organization) => {
- return this.props.handleOrgDetailsFinish({
- ...organization,
- key: this.props.importPersonalOrg.key
- });
- };
-
- render() {
- const { almApplication, importPersonalOrg, organization, step, subscriptionPlans } = this.props;
- return (
- <>
- <OrganizationDetailsStep
- finished={organization !== undefined}
- onOpen={this.props.handleOrgDetailsStepOpen}
- open={step === Step.OrganizationDetails}
- organization={organization}
- stepTitle={translate('onboarding.import_organization.personal.import_org_details')}>
- <div className="display-flex-center big-spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('onboarding.import_personal_organization_x')}
- id="onboarding.import_personal_organization_x"
- values={{
- avatar: (
- <img
- alt={almApplication.name}
- className="little-spacer-left"
- src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(
- almApplication.key
- )}.svg`}
- width={16}
- />
- ),
- name: <strong>{this.props.almOrganization.name}</strong>,
- personalAvatar: importPersonalOrg && (
- <OrganizationAvatar organization={importPersonalOrg} small={true} />
- ),
- personalName: importPersonalOrg && <strong>{importPersonalOrg.name}</strong>
- }}
- />
- <ClearButton className="little-spacer-left" onClick={this.props.handleCancelImport} />
- </div>
- <OrganizationDetailsForm
- keyReadOnly={true}
- onContinue={this.handleOrgDetailsFinish}
- organization={importPersonalOrg}
- submitText={translate('continue')}
- />
- </OrganizationDetailsStep>
- {subscriptionPlans !== undefined && (
- <PlanStep
- almApplication={this.props.almApplication}
- almOrganization={this.props.almOrganization}
- createOrganization={this.handleCreateOrganization}
- onDone={this.props.onDone}
- open={step === Step.Plan}
- subscriptionPlans={subscriptionPlans}
- />
- )}
- </>
- );
- }
-}
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { withRouter, WithRouterProps } from 'react-router';
-import { createOrganization, updateOrganization } from './actions';
+import { createOrganization } from './actions';
import {
ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP,
parseQuery,
} from './utils';
import AlmApplicationInstalling from './AlmApplicationInstalling';
import AutoOrganizationCreate from './AutoOrganizationCreate';
-import AutoPersonalOrganizationBind from './AutoPersonalOrganizationBind';
import ManualOrganizationCreate from './ManualOrganizationCreate';
import RemoteOrganizationChoose from './RemoteOrganizationChoose';
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
} from '../../../api/alm-integration';
import { getSubscriptionPlans } from '../../../api/billing';
import * as api from '../../../api/organizations';
-import {
- hasAdvancedALMIntegration,
- isPersonal,
- sanitizeAlmId
-} from '../../../helpers/almIntegrations';
+import { hasAdvancedALMIntegration, sanitizeAlmId } from '../../../helpers/almIntegrations';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { addWhitePageClass, removeWhitePageClass } from '../../../helpers/pages';
import { get, remove } from '../../../helpers/storage';
) => Promise<string>;
currentUser: T.LoggedInUser;
deleteOrganization: (key: string) => Promise<void>;
- updateOrganization: (
- organization: T.Organization & { installationId?: string }
- ) => Promise<string>;
userOrganizations: T.Organization[];
skipOnboarding: () => void;
}
);
}
- getHeader = (bindingExistingOrg: boolean, importPersonalOrg: boolean) => {
+ getHeader = (bindingExistingOrg: boolean) => {
if (bindingExistingOrg) {
return translate('onboarding.binding_organization');
- } else if (importPersonalOrg) {
- return translate('onboarding.import_organization.personal.page.header');
} else {
return translate('onboarding.create_organization.page.header');
}
});
};
- renderContent = (almInstallId?: string, importPersonalOrg?: T.Organization) => {
- const { currentUser, location } = this.props;
+ renderContent = (almInstallId?: string) => {
+ const { location } = this.props;
const { state } = this;
const { organization, step, subscriptionPlans } = state;
const { tab = 'auto' } = (location.state || {}) as LocationState;
const { almApplication, almOrganization, boundOrganization } = state;
- if (importPersonalOrg && almOrganization && almApplication) {
- return (
- <AutoPersonalOrganizationBind
- {...commonProps}
- almApplication={almApplication}
- almInstallId={almInstallId}
- almOrganization={almOrganization}
- handleCancelImport={this.handleCancelImport}
- importPersonalOrg={importPersonalOrg}
- subscriptionPlans={subscriptionPlans}
- updateOrganization={this.props.updateOrganization}
- />
- );
- }
-
return (
<>
<Tabs<TabKeys>
onOrgCreated={this.handleOrgCreated}
onUpgradeFail={this.deleteOrganization}
unboundOrganizations={this.props.userOrganizations.filter(
- ({ actions = {}, alm, key }) =>
- !alm && key !== currentUser.personalOrganization && actions.admin
+ ({ actions = {}, alm }) => !alm && actions.admin
)}
/>
) : (
};
render() {
- const { currentUser, location } = this.props;
+ const { location } = this.props;
const query = parseQuery(location.query);
if (this.state.almOrgLoading) {
return <AlmApplicationInstalling almKey={query.almKey} />;
}
- const { almOrganization, bindingExistingOrg, subscriptionPlans } = this.state;
- const importPersonalOrg = isPersonal(almOrganization)
- ? this.props.userOrganizations.find(o => o.key === currentUser.personalOrganization)
- : undefined;
- const header = this.getHeader(bindingExistingOrg, !!importPersonalOrg);
-
+ const { bindingExistingOrg, subscriptionPlans } = this.state;
+ const header = this.getHeader(bindingExistingOrg);
const startedPrice = subscriptionPlans && subscriptionPlans[0] && subscriptionPlans[0].price;
return (
<h1 className="page-title huge big-spacer-bottom">
<strong>{header}</strong>
</h1>
- {!importPersonalOrg && startedPrice !== undefined && (
+ {startedPrice !== undefined && (
<p className="page-description">
{translate('onboarding.create_organization.page.description')}
</p>
)}
</header>
- {this.state.loading ? (
- <DeferredSpinner />
- ) : (
- this.renderContent(query.almInstallId, importPersonalOrg)
- )}
+ {this.state.loading ? <DeferredSpinner /> : this.renderContent(query.almInstallId)}
</div>
</>
);
const mapDispatchToProps = {
createOrganization: createOrganization as any,
deleteOrganization: deleteOrganization as any,
- updateOrganization: updateOrganization as any,
skipOnboarding: skipOnboarding as any
};
interface Props {
infoBlock?: React.ReactNode;
- keyReadOnly?: boolean;
onContinue: (organization: T.Organization) => Promise<void>;
organization?: T.Organization;
submitText: string;
render() {
const { submitting } = this.state;
- const { infoBlock, keyReadOnly } = this.props;
+ const { infoBlock } = this.props;
return (
<form id="organization-form" onSubmit={this.handleSubmit}>
- {!keyReadOnly && (
- <OrganizationKeyInput initialValue={this.state.key} onChange={this.handleKeyUpdate} />
- )}
+ <OrganizationKeyInput initialValue={this.state.key} onChange={this.handleKeyUpdate} />
<div className="big-spacer-top">
<ResetButtonLink onClick={this.handleAdditionalClick}>
{translate(
<AutoOrganizationCreate
almApplication={mockAlmApplication()}
almInstallId="id-foo"
- almOrganization={{ ...organization, personal: false }}
+ almOrganization={organization}
createOrganization={jest.fn()}
handleCancelImport={jest.fn()}
handleOrgDetailsFinish={jest.fn()}
+++ /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 { shallow } from 'enzyme';
-import AutoPersonalOrganizationBind from '../AutoPersonalOrganizationBind';
-import { waitAndUpdate, click } from '../../../../helpers/testUtils';
-import { Step } from '../utils';
-import { mockAlmOrganization } from '../../../../helpers/testMocks';
-
-const personalOrg = { key: 'personalorg', name: 'Personal Org' };
-const almOrganization = mockAlmOrganization({ personal: true });
-
-it('should render correctly', async () => {
- const updateOrganization = jest.fn().mockResolvedValue({ key: personalOrg.key });
- const handleOrgDetailsFinish = jest.fn();
- const wrapper = shallowRender({
- almInstallId: 'id-foo',
- importPersonalOrg: personalOrg,
- handleOrgDetailsFinish,
- updateOrganization
- });
-
- expect(wrapper).toMatchSnapshot();
-
- wrapper.find('OrganizationDetailsForm').prop<Function>('onContinue')(personalOrg);
- await waitAndUpdate(wrapper);
- expect(handleOrgDetailsFinish).toBeCalled();
-
- wrapper.setProps({ organization: personalOrg });
- wrapper.find('PlanStep').prop<Function>('createOrganization')();
- expect(updateOrganization).toBeCalledWith({ ...personalOrg, installationId: 'id-foo' });
-});
-
-it('should allow to cancel org import', () => {
- const handleCancelImport = jest.fn();
- const wrapper = shallowRender({
- almInstallId: 'id-foo',
- importPersonalOrg: personalOrg,
- handleCancelImport
- });
-
- click(wrapper.find('ClearButton'));
- expect(handleCancelImport).toBeCalled();
-});
-
-function shallowRender(props: Partial<AutoPersonalOrganizationBind['props']> = {}) {
- return shallow(
- <AutoPersonalOrganizationBind
- almApplication={{
- backgroundColor: '#0052CC',
- iconPath: '"/static/authbitbucket/bitbucket.svg"',
- installationUrl: 'https://bitbucket.org/install/app',
- key: 'bitbucket',
- name: 'BitBucket'
- }}
- almOrganization={almOrganization}
- handleCancelImport={jest.fn()}
- handleOrgDetailsFinish={jest.fn()}
- handleOrgDetailsStepOpen={jest.fn()}
- importPersonalOrg={{ key: 'personalorg', name: 'Personal Org' }}
- onDone={jest.fn()}
- step={Step.OrganizationDetails}
- subscriptionPlans={[{ maxNcloc: 100000, price: 10 }, { maxNcloc: 250000, price: 75 }]}
- updateOrganization={jest.fn()}
- {...props}
- />
- );
-}
description: 'Continuous Code Quality',
key: 'sonarsource',
name: 'SonarSource',
- personal: false,
privateRepos: 0,
publicRepos: 3,
url: 'https://www.sonarsource.com'
}));
const user = mockLoggedInUser();
-const fooAlmOrganization = mockAlmOrganization({ personal: true });
+const fooAlmOrganization = mockAlmOrganization();
const fooBarAlmOrganization = mockAlmOrganization({
avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4',
key: 'Foo&Bar',
- name: 'Foo & Bar',
- personal: true
+ name: 'Foo & Bar'
});
const boundOrganization = { key: 'foobar', name: 'Foo & Bar' };
expect(getOrganizations).toHaveBeenCalled();
});
-it('should render with auto personal organization bind page', async () => {
- (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({
- almOrganization: fooAlmOrganization
- });
- const wrapper = shallowRender({
- currentUser: { ...user, externalProvider: 'github', personalOrganization: 'foo' },
- location: { query: { installation_id: 'foo' } } as Location
- });
- expect(wrapper).toMatchSnapshot();
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
it('should render with organization bind page', async () => {
(getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({
- almOrganization: { ...fooAlmOrganization, personal: false }
+ almOrganization: fooAlmOrganization
});
const wrapper = shallowRender({
currentUser: { ...user, externalProvider: 'github' },
state: { organization: 'foo', tab: 'manual' }
});
- wrapper.setState({ almOrganization: { ...fooAlmOrganization, personal: false } });
+ wrapper.setState({ almOrganization: fooAlmOrganization });
(get as jest.Mock<any>).mockReturnValueOnce(Date.now().toString());
wrapper.instance().handleOrgCreated('foo');
expect(push).toHaveBeenCalledWith({
it('should display AutoOrganizationCreate with already bound organization', async () => {
(getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({
- almOrganization: { ...fooBarAlmOrganization, personal: false },
+ almOrganization: fooBarAlmOrganization,
boundOrganization
});
(get as jest.Mock<any>)
router={mockRouter()}
routes={[]}
skipOnboarding={jest.fn()}
- updateOrganization={jest.fn()}
userOrganizations={[
mockOrganizationWithAdminActions(),
mockOrganizationWithAdminActions(mockOrganizationWithAlm({ key: 'bar', name: 'Bar' })),
it('should preselect paid plan', async () => {
const wrapper = shallow(
<PlanStep
- almOrganization={mockAlmOrganization({ personal: true, privateRepos: 5, publicRepos: 0 })}
+ almOrganization={mockAlmOrganization({ privateRepos: 5, publicRepos: 0 })}
createOrganization={jest.fn()}
onDone={jest.fn()}
onUpgradeFail={jest.fn()}
"description": "description-foo",
"key": "foo",
"name": "foo",
- "personal": false,
"privateRepos": 0,
"publicRepos": 3,
"url": "http://example.com/foo",
"description": "description-foo",
"key": "foo",
"name": "foo",
- "personal": false,
"privateRepos": 0,
"publicRepos": 3,
"url": "http://example.com/foo",
"description": "description-foo",
"key": "foo",
"name": "foo",
- "personal": false,
"privateRepos": 0,
"publicRepos": 3,
"url": "http://example.com/foo",
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
- <OrganizationDetailsStep
- finished={false}
- onOpen={[MockFunction]}
- open={true}
- stepTitle="onboarding.import_organization.personal.import_org_details"
- >
- <div
- className="display-flex-center big-spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="onboarding.import_personal_organization_x"
- id="onboarding.import_personal_organization_x"
- values={
- Object {
- "avatar": <img
- alt="BitBucket"
- className="little-spacer-left"
- src="/images/sonarcloud/bitbucket.svg"
- width={16}
- />,
- "name": <strong>
- foo
- </strong>,
- "personalAvatar": <OrganizationAvatar
- organization={
- Object {
- "key": "personalorg",
- "name": "Personal Org",
- }
- }
- small={true}
- />,
- "personalName": <strong>
- Personal Org
- </strong>,
- }
- }
- />
- <ClearButton
- className="little-spacer-left"
- onClick={[MockFunction]}
- />
- </div>
- <OrganizationDetailsForm
- keyReadOnly={true}
- onContinue={[Function]}
- organization={
- Object {
- "key": "personalorg",
- "name": "Personal Org",
- }
- }
- submitText="continue"
- />
- </OrganizationDetailsStep>
- <PlanStep
- almApplication={
- Object {
- "backgroundColor": "#0052CC",
- "iconPath": "\\"/static/authbitbucket/bitbucket.svg\\"",
- "installationUrl": "https://bitbucket.org/install/app",
- "key": "bitbucket",
- "name": "BitBucket",
- }
- }
- almOrganization={
- Object {
- "almUrl": "https://github.com/foo",
- "avatar": "http://example.com/avatar",
- "description": "description-foo",
- "key": "foo",
- "name": "foo",
- "personal": true,
- "privateRepos": 0,
- "publicRepos": 3,
- "url": "http://example.com/foo",
- }
- }
- createOrganization={[Function]}
- onDone={[MockFunction]}
- open={false}
- subscriptionPlans={
- Array [
- Object {
- "maxNcloc": 100000,
- "price": 10,
- },
- Object {
- "maxNcloc": 250000,
- "price": 75,
- },
- ]
- }
- />
-</Fragment>
-`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render with auto personal organization bind page 1`] = `
-<AlmApplicationInstalling
- almKey="github"
-/>
-`;
-
-exports[`should render with auto personal organization bind page 2`] = `
-<Fragment>
- <HelmetWrapper
- defer={true}
- encodeSpecialCharacters={true}
- title="onboarding.import_organization.personal.page.header"
- titleTemplate="%s"
- />
- <div
- className="page page-limited huge-spacer-top huge-spacer-bottom"
- >
- <A11ySkipTarget
- anchor="create_org_main"
- />
- <header
- className="page-header huge-spacer-bottom"
- >
- <h1
- className="page-title huge big-spacer-bottom"
- >
- <strong>
- onboarding.import_organization.personal.page.header
- </strong>
- </h1>
- </header>
- <AutoPersonalOrganizationBind
- almApplication={
- Object {
- "backgroundColor": "blue",
- "iconPath": "icon/path",
- "installationUrl": "https://alm.installation.url",
- "key": "github",
- "name": "GitHub",
- }
- }
- almInstallId="foo"
- almOrganization={
- Object {
- "almUrl": "https://github.com/foo",
- "avatar": "http://example.com/avatar",
- "description": "description-foo",
- "key": "foo",
- "name": "foo",
- "personal": true,
- "privateRepos": 0,
- "publicRepos": 3,
- "url": "http://example.com/foo",
- }
- }
- handleCancelImport={[Function]}
- handleOrgDetailsFinish={[Function]}
- handleOrgDetailsStepOpen={[Function]}
- importPersonalOrg={
- Object {
- "actions": Object {
- "admin": true,
- },
- "key": "foo",
- "name": "Foo",
- }
- }
- onDone={[Function]}
- step={0}
- subscriptionPlans={
- Array [
- Object {
- "maxNcloc": 100000,
- "price": 10,
- },
- Object {
- "maxNcloc": 250000,
- "price": 75,
- },
- ]
- }
- updateOrganization={[MockFunction]}
- />
- </div>
-</Fragment>
-`;
-
exports[`should render with auto tab displayed 1`] = `
<Fragment>
<HelmetWrapper
"description": "Continuous Code Quality",
"key": "sonarsource",
"name": "SonarSource",
- "personal": false,
"privateRepos": 0,
"publicRepos": 3,
"url": "https://www.sonarsource.com",
"description": "description-foo",
"key": "foo",
"name": "foo",
- "personal": false,
"privateRepos": 0,
"publicRepos": 3,
"url": "http://example.com/foo",
"description": "description-foo",
"key": "foo",
"name": "foo",
- "personal": true,
"privateRepos": 5,
"publicRepos": 0,
"url": "http://example.com/foo",
* 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 actions from '../actions';
+import { createOrganization, syncMembers } from '../../../../api/organizations';
import { mockOrganization, mockOrganizationWithAlm } from '../../../../helpers/testMocks';
-import { createOrganization, syncMembers, updateOrganization } from '../../../../api/organizations';
-import { bindAlmOrganization } from '../../../../api/alm-integration';
+import * as actions from '../actions';
jest.mock('../../../../api/alm-integration', () => ({
bindAlmOrganization: jest.fn().mockResolvedValue({})
expect(syncMembers).toHaveBeenCalledWith(org.key);
});
});
-
-describe('#updateOrganization', () => {
- it('should update and dispatch', async () => {
- const org = mockOrganization();
- const { key, ...changes } = org;
- const promise = actions.updateOrganization(org)(dispatch);
-
- expect(updateOrganization).toHaveBeenCalledWith(key, changes);
- const returnValue = await promise;
- expect(dispatch).toHaveBeenCalledWith({ changes, key, type: 'UPDATE_ORGANIZATION' });
- expect(returnValue).toBe(key);
- });
-
- it('should update and bind', () => {
- const org = { ...mockOrganization(), installationId: '1' };
- const { key, installationId, ...changes } = org;
- const promise = actions.updateOrganization(org)(dispatch);
-
- expect(updateOrganization).toHaveBeenCalledWith(key, changes);
- expect(bindAlmOrganization).toHaveBeenCalledWith({ organization: key, installationId });
- return promise;
- });
-});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Dispatch } from 'redux';
-import { bindAlmOrganization } from '../../../api/alm-integration';
import * as api from '../../../api/organizations';
import * as actions from '../../../store/organizations';
import { isGithub } from '../../../helpers/almIntegrations';
});
};
}
-
-export function updateOrganization(organization: T.Organization & { installationId?: string }) {
- return (dispatch: Dispatch) => {
- const { key, installationId, ...changes } = organization;
- const promises = [api.updateOrganization(key, changes)];
- if (installationId) {
- promises.push(bindAlmOrganization({ organization: key, installationId }));
- }
- return Promise.all(promises).then(() => {
- dispatch(actions.updateOrganization(key, changes));
- return organization.key;
- });
- };
-}
import { formatMeasure } from '../../helpers/measures';
export interface Props {
- currentUser: T.LoggedInUser;
handleSearch: (query?: string) => void;
organization: T.Organization;
total?: number;
}
-export default function MembersListHeader({
- currentUser,
- handleSearch,
- organization,
- total
-}: Props) {
+export default function MembersListHeader({ handleSearch, organization, total }: Props) {
return (
<div className="panel panel-vertical bordered-bottom spacer-bottom">
<SearchBox
sanitizeAlmId(organization.alm.key)
)}
</p>
- {currentUser.personalOrganization !== organization.key && (
- <>
- <hr />
- <p>
- <a
- href={getAlmMembersUrl(organization.alm.key, organization.alm.url)}
- rel="noopener noreferrer"
- target="_blank">
- {translateWithParameters(
- 'organization.members.see_all_members_on_x',
- translate(sanitizeAlmId(organization.alm.key))
- )}
- </a>
- </p>
- </>
+ <hr />
+ <p>
+ <a
+ href={getAlmMembersUrl(organization.alm.key, organization.alm.url)}
+ rel="noopener noreferrer"
+ target="_blank">
+ {translateWithParameters(
+ 'organization.members.see_all_members_on_x',
+ translate(sanitizeAlmId(organization.alm.key))
+ )}
+ </a>
+ </p>
)}
</div>
}
{members !== undefined && paging !== undefined && (
<>
<MembersListHeader
- currentUser={currentUser}
handleSearch={this.handleSearchMembers}
organization={organization}
total={paging.total}
import * as React from 'react';
import { shallow } from 'enzyme';
import MembersListHeader, { Props } from '../MembersListHeader';
-import {
- mockLoggedInUser,
- mockOrganization,
- mockOrganizationWithAlm
-} from '../../../helpers/testMocks';
+import { mockOrganization, mockOrganizationWithAlm } from '../../../helpers/testMocks';
it('should render without the total', () => {
expect(shallowRender({ total: undefined })).toMatchSnapshot();
).toMatchSnapshot();
});
-it('should not render link in help tooltip', () => {
- expect(
- shallowRender({
- currentUser: mockLoggedInUser({ personalOrganization: 'foo' }),
- organization: mockOrganizationWithAlm({}, { membersSync: true })
- }).find('HelpTooltip')
- ).toMatchSnapshot();
-});
-
function shallowRender(props: Partial<Props> = {}) {
return shallow(
<MembersListHeader
- currentUser={mockLoggedInUser()}
handleSearch={jest.fn()}
organization={mockOrganization()}
total={8}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should not render link in help tooltip 1`] = `
+exports[`should render a help tooltip 1`] = `
<HelpTooltip
className="spacer-left"
overlay={
<p>
organization.members.auto_sync_total_help.github
</p>
- </div>
- }
-/>
-`;
-
-exports[`should render a help tooltip 1`] = `
-<HelpTooltip
- className="spacer-left"
- overlay={
- <div
- className="abs-width-300 markdown cut-margins"
- >
+ <hr />
<p>
- organization.members.auto_sync_total_help.github
+ <a
+ href="https://github.com/orgs/foo/people"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ organization.members.see_all_members_on_x.github
+ </a>
</p>
- <React.Fragment>
- <hr />
- <p>
- <a
- href="https://github.com/orgs/foo/people"
- rel="noopener noreferrer"
- target="_blank"
- >
- organization.members.see_all_members_on_x.github
- </a>
- </p>
- </React.Fragment>
+ )}
</div>
}
/>
<p>
organization.members.auto_sync_total_help.bitbucket
</p>
- <React.Fragment>
- <hr />
- <p>
- <a
- href="https://bitbucket.com/foo/profile/members"
- rel="noopener noreferrer"
- target="_blank"
- >
- organization.members.see_all_members_on_x.bitbucket
- </a>
- </p>
- </React.Fragment>
+ <hr />
+ <p>
+ <a
+ href="https://bitbucket.com/foo/profile/members"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ organization.members.see_all_members_on_x.bitbucket
+ </a>
+ </p>
+ )}
</div>
}
/>
refreshMembers={[Function]}
/>
<MembersListHeader
- currentUser={
- Object {
- "groups": Array [],
- "isLoggedIn": true,
- "login": "luke",
- "name": "Skywalker",
- "scmAccounts": Array [],
- }
- }
handleSearch={[Function]}
organization={
Object {
);
}
-export function OrganizationAdminAccess(props: OwnProps) {
+export default function OrganizationAdminAccess(props: OwnProps) {
return <OrganizationAccessContainer hasAccess={hasAdminAccess} {...props} />;
}
childRoutes: qualityGatesRoutes
},
{
- component: lazyLoad(() =>
- import('./components/OrganizationAccessContainer').then(lib => ({
- default: lib.OrganizationAdminAccess
- }))
- ),
+ component: lazyLoad(() => import('./components/OrganizationAccessContainer')),
childRoutes: [
- { path: 'delete', component: lazyLoad(() => import('./components/OrganizationDelete')) },
{ path: 'edit', component: lazyLoad(() => import('./components/OrganizationEdit')) },
{ path: 'groups', component: lazyLoad(() => import('../groups/components/App')) },
{
import '../styles.css';
export interface Props {
- currentUser: T.LoggedInUser;
onClose: VoidFunction;
onOpenProjectOnboarding: VoidFunction;
userOrganizations: T.Organization[];
}
export function OnboardingModal(props: Props) {
- const { currentUser, onClose, onOpenProjectOnboarding, userOrganizations } = props;
-
- const organizations = userOrganizations.filter(o => o.key !== currentUser.personalOrganization);
+ const { onClose, onOpenProjectOnboarding, userOrganizations } = props;
const header = translate('onboarding.header');
return (
contentLabel={header}
onRequestClose={onClose}
shouldCloseOnOverlayClick={false}
- size={organizations.length > 0 ? 'medium' : 'small'}>
+ size={userOrganizations.length > 0 ? 'medium' : 'small'}>
<div className="modal-head">
<h2>{translate('onboarding.header')}</h2>
<p className="spacer-top">{translate('onboarding.header.description')}</p>
{translate('onboarding.project.create')}
</Button>
</div>
- {organizations.length > 0 && (
+ {userOrganizations.length > 0 && (
<>
<div className="vertical-pipe-separator">
<div className="vertical-separator" />
<h3 className="big-spacer-bottom">
{translate('onboarding.browse_your_organizations')}
</h3>
- <OrganizationsShortList onClick={onClose} organizations={organizations} />
+ <OrganizationsShortList onClick={onClose} organizations={userOrganizations} />
</div>
</>
)}
import { shallow } from 'enzyme';
import { OnboardingModal, Props } from '../OnboardingModal';
import { click } from '../../../../helpers/testUtils';
-import { mockLoggedInUser, mockOrganization } from '../../../../helpers/testMocks';
+import { mockOrganization } from '../../../../helpers/testMocks';
it('renders correctly', () => {
expect(shallowRender()).toMatchSnapshot();
it('should display organization list if any', () => {
const wrapper = shallowRender({
- currentUser: mockLoggedInUser({ personalOrganization: 'personal' }),
userOrganizations: [
mockOrganization({ key: 'a', name: 'Arthur' }),
- mockOrganization({ key: 'd', name: 'Daniel Inc' }),
- mockOrganization({ key: 'personal', name: 'Personal' })
+ mockOrganization({ key: 'b', name: 'Boston Co' }),
+ mockOrganization({ key: 'd', name: 'Daniel Inc' })
]
});
expect(wrapper).toMatchSnapshot();
- expect(wrapper.find('OrganizationsShortList').prop('organizations')).toHaveLength(2);
+ expect(wrapper.find('OrganizationsShortList').prop('organizations')).toHaveLength(3);
});
function shallowRender(props: Partial<Props> = {}) {
return shallow(
<OnboardingModal
- currentUser={mockLoggedInUser()}
onClose={jest.fn()}
onOpenProjectOnboarding={jest.fn()}
userOrganizations={[]}
"key": "a",
"name": "Arthur",
},
+ Object {
+ "key": "b",
+ "name": "Boston Co",
+ },
Object {
"key": "d",
"name": "Daniel Inc",
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-export function getWrappedDisplayName(WrappedComponent: React.ComponentType, hocName: string) {
+export function getWrappedDisplayName<P>(
+ WrappedComponent: React.ComponentType<P>,
+ hocName: string
+) {
const wrappedDisplayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
return `${hocName}(${wrappedDisplayName})`;
}
import {
isBitbucket,
isGithub,
- isPersonal,
isVSTS,
sanitizeAlmId,
getAlmMembersUrl,
expect(isVSTS('github')).toBeFalsy();
});
-it('#isPersonal', () => {
- const almOrg = {
- almUrl: '',
- key: 'foo',
- name: 'Foo',
- personal: true,
- privateRepos: 0,
- publicRepos: 3
- };
- expect(isPersonal(almOrg)).toBeTruthy();
- expect(isPersonal({ ...almOrg, personal: false })).toBeFalsy();
-});
-
it('#sanitizeAlmId', () => {
expect(sanitizeAlmId('bitbucketcloud')).toBe('bitbucket');
expect(sanitizeAlmId('bitbucket')).toBe('bitbucket');
return almKey === 'microsoft';
}
-export function isPersonal(organization?: T.AlmOrganization) {
- return Boolean(organization && organization.personal);
-}
-
export function sanitizeAlmId(almKey: string) {
if (isBitbucket(almKey)) {
return 'bitbucket';
description: 'description-foo',
key: 'foo',
name: 'foo',
- personal: false,
privateRepos: 0,
publicRepos: 3,
url: 'http://example.com/foo',
onboarding.import_organization.choose_the_organization_button.bitbucket=Choose the team on Bitbucket
onboarding.import_organization.choose_the_organization_button.github=Choose the organization on GitHub
onboarding.import_organization.installing=Finalize installation of the {0} application...
-onboarding.import_organization.personal.page.header=Bind to your personal organization
onboarding.import_organization.personal.import_org_details=Import personal organization details
onboarding.import_organization.private.disabled=Selecting private repository is not available yet and will come soon. Meanwhile, you need to create the project manually.
onboarding.import_organization.import_from_x=Import from {0}