Browse Source

SONARCLOUD-380 Drop members sync feature for Bitbucket

tags/7.7
Grégoire Aubert 5 years ago
parent
commit
7e37d5382c

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java View File

@@ -26,5 +26,5 @@ import org.sonar.db.organization.OrganizationDto;
@ServerSide
public interface OrganizationAlmBinding {

void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId, boolean enableMembersSync);
void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId, boolean isNewOrganization);
}

+ 16
- 12
server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationBind.tsx View File

@@ -24,6 +24,7 @@ import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { Alert } from '../../../components/ui/Alert';
import { SubmitButton } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { isGithub } from '../../../helpers/almIntegrations';

interface Props {
almKey: string;
@@ -79,6 +80,7 @@ export default class AutoOrganizationBind extends React.PureComponent<Props, Sta
};

render() {
const { almKey } = this.props;
const { organization, submitting } = this.state;
return (
<form id="bind-organization-form" onSubmit={this.handleSubmit}>
@@ -87,18 +89,20 @@ export default class AutoOrganizationBind extends React.PureComponent<Props, Sta
organization={organization}
organizations={this.props.unboundOrganizations}
/>
<Alert className="abs-width-400 big-spacer-top" display="block" variant="info">
{translateWithParameters(
'onboarding.import_organization.bind_members_not_sync_info_x',
translate('organization', this.props.almKey)
)}
<Link
className="spacer-left"
target="_blank"
to={{ pathname: '/documentation/organizations/manage-team/' }}>
{translate('learn_more')}
</Link>
</Alert>
{isGithub(almKey) && (
<Alert className="abs-width-400 big-spacer-top" display="block" variant="info">
{translateWithParameters(
'onboarding.import_organization.bind_members_not_sync_info_x',
translate('organization', almKey)
)}
<Link
className="spacer-left"
target="_blank"
to={{ pathname: '/documentation/organizations/manage-team/' }}>
{translate('learn_more')}
</Link>
</Alert>
)}
<div className="display-flex-center big-spacer-top">
<SubmitButton disabled={submitting || !organization}>
{translate('onboarding.import_organization.bind')}

+ 22
- 20
server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx View File

@@ -28,7 +28,7 @@ import { Alert } from '../../../components/ui/Alert';
import { DeleteButton } from '../../../components/ui/buttons';
import RadioToggle from '../../../components/controls/RadioToggle';
import { bindAlmOrganization } from '../../../api/alm-integration';
import { sanitizeAlmId, getAlmMembersUrl } from '../../../helpers/almIntegrations';
import { sanitizeAlmId, getAlmMembersUrl, isGithub } from '../../../helpers/almIntegrations';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';

@@ -164,25 +164,27 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props, S
{filter === Filters.Create && (
<OrganizationDetailsForm
infoBlock={
<Alert className="abs-width-600 big-spacer-top" display="block" variant="info">
<p>
{translateWithParameters(
'onboarding.import_organization.members_sync_info_x',
translate('organization', almKey),
almOrganization.name,
translate(almKey)
)}
</p>
<a
href={getAlmMembersUrl(almApplication.key, almOrganization.almUrl)}
rel="noopener noreferrer"
target="_blank">
{translateWithParameters(
'organization.members.see_all_members_on_x',
translate(almKey)
)}
</a>
</Alert>
isGithub(almKey) && (
<Alert className="abs-width-600 big-spacer-top" display="block" variant="info">
<p>
{translateWithParameters(
'onboarding.import_organization.members_sync_info_x',
translate('organization', almKey),
almOrganization.name,
translate(almKey)
)}
</p>
<a
href={getAlmMembersUrl(almApplication.key, almOrganization.almUrl)}
rel="noopener noreferrer"
target="_blank">
{translateWithParameters(
'organization.members.see_all_members_on_x',
translate(almKey)
)}
</a>
</Alert>
)
}
onContinue={this.props.handleOrgDetailsFinish}
organization={almOrganization}

+ 8
- 0
server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationBind-test.tsx View File

@@ -32,6 +32,14 @@ it('should render correctly', () => {
expect(onBindOrganization).toHaveBeenCalled();
});

it('should not show member sync info box for Bitbucket', () => {
expect(
shallowRender({ almKey: 'bitbucket' })
.find('Alert')
.exists()
).toBe(false);
});

function shallowRender(props: Partial<AutoOrganizationBind['props']> = {}) {
return shallow(
<AutoOrganizationBind

+ 8
- 2
server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx View File

@@ -34,10 +34,8 @@ const organization = mockAlmOrganization();
it('should render prefilled and create org', async () => {
const createOrganization = jest.fn().mockResolvedValue({ key: 'foo' });
const handleOrgDetailsFinish = jest.fn();
const almApplication = mockAlmApplication({ key: 'github' });
const almOrganization = mockAlmOrganization({ almUrl: 'http://github.com/thing' });
const wrapper = shallowRender({
almApplication,
almOrganization,
createOrganization,
handleOrgDetailsFinish
@@ -96,6 +94,14 @@ it('should bind existing organization', async () => {
expect(onOrgCreated).toHaveBeenCalledWith('foo');
});

it('should not show member sync info box for Bitbucket', () => {
expect(
shallowRender({ almApplication: mockAlmApplication({ key: 'bitbucket-cloud' }) })
.find('Alert')
.exists()
).toBe(false);
});

function shallowRender(props: Partial<AutoOrganizationCreate['props']> = {}) {
return shallow(
<AutoOrganizationCreate

+ 12
- 12
server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap View File

@@ -20,9 +20,9 @@ exports[`should display choice between import or creation 1`] = `
values={
Object {
"avatar": <img
alt="BitBucket"
alt="GitHub"
className="little-spacer-left"
src="/images/sonarcloud/bitbucket.svg"
src="/images/sonarcloud/github.svg"
width={16}
/>,
"name": <strong>
@@ -59,11 +59,11 @@ exports[`should display choice between import or creation 1`] = `
<PlanStep
almApplication={
Object {
"backgroundColor": "#0052CC",
"iconPath": "\\"/static/authbitbucket/bitbucket.svg\\"",
"installationUrl": "https://bitbucket.org/install/app",
"key": "bitbucket",
"name": "BitBucket",
"backgroundColor": "#444444",
"iconPath": "/images/sonarcloud/github-white.svg",
"installationUrl": "https://github.com/apps/greg-sonarcloud/installations/new",
"key": "github",
"name": "GitHub",
}
}
almOrganization={
@@ -119,7 +119,7 @@ exports[`should render prefilled and create org 1`] = `
values={
Object {
"avatar": <img
alt="BitBucket"
alt="GitHub"
className="little-spacer-left"
src="/images/sonarcloud/github.svg"
width={16}
@@ -175,11 +175,11 @@ exports[`should render prefilled and create org 1`] = `
<PlanStep
almApplication={
Object {
"backgroundColor": "#0052CC",
"iconPath": "\\"/static/authbitbucket/bitbucket.svg\\"",
"installationUrl": "https://bitbucket.org/install/app",
"backgroundColor": "#444444",
"iconPath": "/images/sonarcloud/github-white.svg",
"installationUrl": "https://github.com/apps/greg-sonarcloud/installations/new",
"key": "github",
"name": "BitBucket",
"name": "GitHub",
}
}
almOrganization={

+ 3
- 1
server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx View File

@@ -24,7 +24,7 @@ import AddMemberForm from './AddMemberForm';
import SyncMemberForm from './SyncMemberForm';
import DeferredSpinner from '../../components/common/DeferredSpinner';
import DocTooltip from '../../components/docs/DocTooltip';
import { sanitizeAlmId } from '../../helpers/almIntegrations';
import { sanitizeAlmId, isGithub } from '../../helpers/almIntegrations';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { Alert } from '../../components/ui/Alert';

@@ -51,6 +51,7 @@ export default function MembersPageHeader(props: Props) {
{isAdmin && (
<div className="page-actions text-right">
{almKey &&
isGithub(almKey) &&
!showSyncNotif && (
<SyncMemberForm organization={organization} refreshMembers={refreshMembers} />
)}
@@ -82,6 +83,7 @@ export default function MembersPageHeader(props: Props) {
}}
/>
{almKey &&
isGithub(almKey) &&
showSyncNotif && (
<Alert className="spacer-top" display="inline" variant="info">
{translateWithParameters(

+ 2
- 2
server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx View File

@@ -25,7 +25,7 @@ import RadioCard from '../../components/controls/RadioCard';
import { Alert } from '../../components/ui/Alert';
import { Button } from '../../components/ui/buttons';
import { setOrganizationMemberSync, syncMembers } from '../../api/organizations';
import { sanitizeAlmId, isGithub } from '../../helpers/almIntegrations';
import { sanitizeAlmId } from '../../helpers/almIntegrations';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { fetchOrganization } from '../../store/rootActions';

@@ -56,7 +56,7 @@ export class SyncMemberForm extends React.PureComponent<Props, State> {
enabled: membersSync
}).then(() => {
this.props.fetchOrganization(organization.key);
if (membersSync && isGithub(organization.alm && organization.alm.key)) {
if (membersSync) {
return this.handleMemberSync();
}
return Promise.resolve();

+ 8
- 1
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx View File

@@ -36,7 +36,14 @@ it('should render for admin', () => {
).toMatchSnapshot();
});

it('should render for bound organization without sync', () => {
it('should render for Bitbucket bound organization', () => {
const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions(), {
key: 'bitbucket'
});
expect(shallowRender({ organization })).toMatchSnapshot();
});

it('should render for GitHub bound organization without sync', () => {
const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions());
expect(shallowRender({ organization })).toMatchSnapshot();
});

+ 1
- 18
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx View File

@@ -33,7 +33,7 @@ beforeEach(() => {
jest.clearAllMocks();
});

it('should allow to switch to automatic mode with github', async () => {
it('should allow to switch to automatic mode', async () => {
const fetchOrganization = jest.fn();
const refreshMembers = jest.fn().mockResolvedValue({});
const wrapper = shallowRender({ fetchOrganization, refreshMembers });
@@ -49,23 +49,6 @@ it('should allow to switch to automatic mode with github', async () => {
expect(refreshMembers).toBeCalled();
});

it('should allow to switch to automatic mode with bitbucket', async () => {
const fetchOrganization = jest.fn();
const wrapper = shallowRender({
fetchOrganization,
organization: mockOrganizationWithAlm({}, { key: 'bitbucket' })
});
expect(wrapper).toMatchSnapshot();

wrapper.setState({ membersSync: true });
wrapper.find('ConfirmButton').prop<Function>('onConfirm')();
expect(setOrganizationMemberSync).toHaveBeenCalledWith({ organization: 'foo', enabled: true });

await waitAndUpdate(wrapper);
expect(fetchOrganization).toHaveBeenCalledWith('foo');
expect(syncMembers).not.toHaveBeenCalled();
});

it('should allow to switch to manual mode', async () => {
const fetchOrganization = jest.fn();
const wrapper = shallowRender({

+ 68
- 2
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap View File

@@ -36,7 +36,7 @@ exports[`should render correctly 1`] = `
</header>
`;

exports[`should render for admin 1`] = `
exports[`should render for Bitbucket bound organization 1`] = `
<header
className="page-header"
>
@@ -63,6 +63,11 @@ exports[`should render for admin 1`] = `
"actions": Object {
"admin": true,
},
"alm": Object {
"key": "bitbucket",
"membersSync": false,
"url": "https://github.com/foo",
},
"key": "foo",
"name": "Foo",
}
@@ -97,7 +102,7 @@ exports[`should render for admin 1`] = `
</header>
`;

exports[`should render for bound organization without sync 1`] = `
exports[`should render for GitHub bound organization without sync 1`] = `
<header
className="page-header"
>
@@ -190,3 +195,64 @@ exports[`should render for bound organization without sync 1`] = `
</div>
</header>
`;

exports[`should render for admin 1`] = `
<header
className="page-header"
>
<h1
className="page-title"
>
organization.members.page
</h1>
<DeferredSpinner
loading={false}
timeout={100}
/>
<div
className="page-actions text-right"
>
<div
className="display-inline-block spacer-left spacer-bottom"
>
<AddMemberForm
addMember={[MockFunction]}
memberLogins={Array []}
organization={
Object {
"actions": Object {
"admin": true,
},
"key": "foo",
"name": "Foo",
}
}
/>
<DocTooltip
className="spacer-left"
doc={Promise {}}
/>
</div>
</div>
<div
className="page-description"
>
<FormattedMessage
defaultMessage="organization.members.page.description"
id="organization.members.page.description"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/organizations/manage-team/"
>
organization.members.manage_a_team
</Link>,
}
}
/>
</div>
</header>
`;

+ 1
- 97
server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap View File

@@ -1,102 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should allow to switch to automatic mode with bitbucket 1`] = `
<ConfirmButton
cancelButtonText="close"
confirmButtonText="save"
confirmDisable={true}
modalBody={
<div
className="display-flex-stretch big-spacer-top"
>
<RadioCard
onClick={[Function]}
selected={true}
title="organization.members.management.manual"
>
<div
className="spacer-left"
>
<ul
className="big-spacer-left note"
>
<li
className="spacer-bottom"
>
organization.members.management.manual.add_members_manually
</li>
<li>
organization.members.management.choose_members_permissions
</li>
</ul>
</div>
</RadioCard>
<RadioCard
onClick={[Function]}
selected={false}
title="organization.members.management.automatic.bitbucket"
>
<div
className="spacer-left"
>
<ul
className="big-spacer-left note"
>
<React.Fragment>
<li
className="spacer-bottom"
>
organization.members.management.automatic.synchronized_from_x.organization.bitbucket
</li>
<li
className="spacer-bottom"
>
organization.members.management.automatic.members_changes_reflected.bitbucket
</li>
</React.Fragment>
<li>
organization.members.management.choose_members_permissions
</li>
</ul>
</div>
<Alert
className="big-spacer-top"
variant="warning"
>
organization.members.management.automatic.warning
</Alert>
</RadioCard>
</div>
}
modalHeader="organization.members.management.title"
modalHeaderDescription={
<p
className="spacer-top"
>
organization.members.management.description
<Link
className="spacer-left"
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to={
Object {
"pathname": "/documentation/organizations/manage-team/",
}
}
>
learn_more
</Link>
</p>
}
onConfirm={[Function]}
size="medium"
>
<Component />
</ConfirmButton>
`;

exports[`should allow to switch to automatic mode with github 1`] = `
exports[`should allow to switch to automatic mode 1`] = `
<ConfirmButton
cancelButtonText="close"
confirmButtonText="save"

+ 5
- 5
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -23,11 +23,11 @@ import { Profile } from '../apps/quality-profiles/types';

export function mockAlmApplication(overrides: Partial<T.AlmApplication> = {}): T.AlmApplication {
return {
backgroundColor: '#0052CC',
iconPath: '"/static/authbitbucket/bitbucket.svg"',
installationUrl: 'https://bitbucket.org/install/app',
key: 'bitbucket',
name: 'BitBucket',
backgroundColor: '#444444',
iconPath: '/images/sonarcloud/github-white.svg',
installationUrl: 'https://github.com/apps/greg-sonarcloud/installations/new',
key: 'github',
name: 'GitHub',
...overrides
};
}

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

@@ -2680,7 +2680,6 @@ organization.members.add_to_members=Add to members
organization.members.config_synchro=Configure Synchronization
organization.members.auto_sync_with_x=Automatic sync with {0}
organization.members.auto_sync_members_from_org_x=Now your members can be automatically synchronized with your {0}.
organization.members.auto_sync_total_help.bitbucket=You might not see all members from your Bitbucket team yet, as they need to reconnect to SonarCloud to be members of the organization.
organization.members.auto_sync_total_help.github=You might not see all members from your GitHub organization yet, as they need to connect to SonarCloud at least once to appear in this list.
organization.members.see_all_members_on_x=See all members on {0}
organization.members.management.title=Members Management
@@ -2689,7 +2688,6 @@ organization.members.management.manual=Manual
organization.members.management.manual.add_members_manually=Admin add members manually from SonarCloud existing users
organization.members.management.automatic=Automatic sync with {0}
organization.members.management.automatic.synchronized_from_x=Members are synchronized automatically from your {0}
organization.members.management.automatic.members_changes_reflected.bitbucket=Your team members must reconnect to SonarCloud to be automatically added to correct SonarCloud organization
organization.members.management.automatic.members_changes_reflected.github=If you add or remove a member on GitHub, SonarCloud immediately reflects the changes
organization.members.management.automatic.warning=This will override your current Members and Permissions configuration
organization.members.management.choose_members_permissions=Admin manages permissions for each member in SonarCloud

Loading…
Cancel
Save