window.bbcJWT = '%BBC_JWT%';
window.bbcWidgetKey = '%BBC_WIDGET_KEY%';
window.projectKey = '%BBC_PROJECT_KEY%';
+ window.allowManualBinding = '%BBC_ALLOW_MANUAL_BINDING%';
</script>
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="%WEB_CONTEXT%<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
import SonarCloudIcon from './SonarCloudIcon';
import { AppContext } from '../types';
import { bindProject, displayWSError, getMyProjects, putStoredProperty } from '../api';
-import { displayMessage } from '../utils';
+import { displayMessage, isManualBindingAllowed } from '../utils';
interface ProjectOption {
label: string;
authenticated: false,
saving: false,
disabled: props.disabled,
- loading: true,
+ loading: isManualBindingAllowed(),
projects: []
};
}
componentDidMount() {
this.mounted = true;
- this.fetchMyProjects();
+ if (isManualBindingAllowed()) {
+ this.fetchMyProjects();
+ }
}
componentWillUnmount() {
}
const { selectedProject } = this;
- if (selectedProject && selectedProject.value !== this.props.projectKey) {
+ if (
+ isManualBindingAllowed() &&
+ selectedProject &&
+ selectedProject.value !== this.props.projectKey
+ ) {
updateBinding = true;
promises.push(
bindProject({ ...this.props.context, projectKey: selectedProject.value }).then(() => {
})
);
}
+
if (promises.length > 0) {
(document.activeElement as HTMLElement).blur();
this.setState({ saving: true });
};
renderContainer = (children: React.ReactNode) => {
+ const { projectKey } = this.props;
+ let descriptionComponent: React.ReactNode = (
+ <>
+ To display the quality of your repository, you have to{' '}
+ <a href={getBaseUrl() + '/projects/create'}>provision</a> a project on SonarCloud and
+ trigger an analysis.
+ </>
+ );
+
+ if (isManualBindingAllowed()) {
+ descriptionComponent =
+ 'To display the quality of your repository, you have to link it with a project analyzed on SonarCloud.';
+ } else if (projectKey) {
+ descriptionComponent = (
+ <>
+ This repository is already bound to a{' '}
+ <a href={getBaseUrl() + '/dashboard?id=' + encodeURIComponent(projectKey)}>
+ SonarCloud project
+ </a>
+ </>
+ );
+ }
+
return (
<>
<h2>SonarCloud Settings</h2>
<div className="settings-logo">
<SonarCloudIcon size={128} />
</div>
- <p className="settings-description">
- To display the quality of your repository, you have to link it with a project analyzed
- on SonarCloud.
- </p>
+ <p className="settings-description">{descriptionComponent}</p>
{children}
</div>
</>
render() {
const { authenticated, disabled, loading } = this.state;
-
if (loading) {
return this.renderContainer(
<div className="huge-spacer-top">
return this.renderContainer(
<>
- {!authenticated && <LoginForm onReload={this.handleReload} />}
+ {isManualBindingAllowed() && !authenticated && <LoginForm onReload={this.handleReload} />}
<form className="settings-form" onSubmit={this.handleSubmit}>
- {authenticated && this.renderProjectsSelect()}
+ {isManualBindingAllowed() && authenticated && this.renderProjectsSelect()}
<div className="display-flex-justify-center">
<CheckboxStateless
isChecked={!disabled}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { getBaseUrl } from '@sqcore/helpers/urls';
import SonarCloudIcon from './SonarCloudIcon';
-import { getRepoSettingsUrl, isRepoAdmin } from '../utils';
+import { getRepoSettingsUrl, isRepoAdmin, isManualBindingAllowed } from '../utils';
interface State {
settingsUrl?: string;
render() {
const { settingsUrl } = this.state;
- const settingsLink = settingsUrl ? (
- <a href={settingsUrl} rel="noopener noreferrer" target="_parent">
- repository settings
- </a>
- ) : (
- 'repository settings'
- );
+ let msgComponent: React.ReactNode =
+ 'Contact a repository administrator to link the repository with a SonarCloud project.';
+ if (isRepoAdmin()) {
+ if (isManualBindingAllowed()) {
+ msgComponent = (
+ <>
+ You have to link your repository with an existing SonarCloud project. Go to your{' '}
+ {settingsUrl ? (
+ <a href={settingsUrl} rel="noopener noreferrer" target="_parent">
+ repository settings
+ </a>
+ ) : (
+ 'repository settings'
+ )}.
+ </>
+ );
+ } else {
+ msgComponent = (
+ <>
+ You have to <a href={getBaseUrl() + '/projects/create'}>provision</a> a project in
+ SonarCloud for this repository.
+ </>
+ );
+ }
+ }
+
return (
<>
<div className="project-card-header">
<h4 className="spacer-left">Code Quality</h4>
</div>
</div>
- <div className="spacer-top">
- {isRepoAdmin() ? (
- <>
- You have to link your repository with an existing SonarCloud project. Go to your{' '}
- {settingsLink}.
- </>
- ) : (
- 'Contact a repository administrator to link the repository with a SonarCloud project.'
- )}
- </div>
+ <div className="spacer-top">{msgComponent}</div>
</>
);
}
import { shallow } from 'enzyme';
import Config from '../Config';
import { bindProject, getMyProjects } from '../../api';
+import { isManualBindingAllowed } from '../../utils';
jest.mock('../../utils', () => ({
- displayMessage: jest.fn()
+ displayMessage: jest.fn(),
+ isManualBindingAllowed: jest.fn(() => true)
}));
jest.mock('../../api', () => ({
expect(wrapper).toMatchSnapshot();
});
+it('should display correctly for auto binding', () => {
+ (isManualBindingAllowed as jest.Mock<any>).mockReturnValue(false);
+ expect(getWrapper()).toMatchSnapshot();
+ (isManualBindingAllowed as jest.Mock<any>).mockReturnValue(true);
+});
+
+it('should display correctly for auto binding with already a projectKey', () => {
+ (isManualBindingAllowed as jest.Mock<any>).mockReturnValue(false);
+ expect(getWrapper()).toMatchSnapshot();
+ (isManualBindingAllowed as jest.Mock<any>).mockReturnValue(true);
+});
+
it('should display the authentication component and the display checkbox', async () => {
(getMyProjects as jest.Mock<any>).mockImplementationOnce(() =>
Promise.reject({ response: { status: 401 } })
import * as React from 'react';
import { shallow } from 'enzyme';
import RepoWidgetNotConfigured from '../RepoWidgetNotConfigured';
+import { isManualBindingAllowed, isRepoAdmin } from '../../utils';
jest.mock('../../utils', () => ({
- getRepoSettingsUrl: jest.fn(() =>
- Promise.resolve(
+ getRepoSettingsUrl: jest
+ .fn()
+ .mockResolvedValue(
'https://bitbucketcloud.org/{}/{repo_uuid}/admin/addon/admin/app-key/repository-config'
- )
- ),
- isRepoAdmin: jest.fn(() => true)
+ ),
+ isRepoAdmin: jest.fn().mockReturnValue(true),
+ isManualBindingAllowed: jest.fn().mockReturnValue(true)
}));
-it('should display correctly', async () => {
+it('should display correctly for admin', async () => {
const wrapper = shallow(<RepoWidgetNotConfigured />);
expect(wrapper).toMatchSnapshot();
wrapper.update();
expect(wrapper).toMatchSnapshot();
});
+
+it('should display correctly for non admin', () => {
+ (isRepoAdmin as jest.Mock<any>).mockReturnValueOnce(false);
+ expect(shallow(<RepoWidgetNotConfigured />)).toMatchSnapshot();
+});
+
+it('should display a link to SonarCloud for auto provisioning', () => {
+ (isManualBindingAllowed as jest.Mock<any>).mockReturnValueOnce(false);
+ expect(shallow(<RepoWidgetNotConfigured />)).toMatchSnapshot();
+});
</React.Fragment>
`;
+exports[`should display correctly for auto binding 1`] = `
+<React.Fragment>
+ <h2>
+ SonarCloud Settings
+ </h2>
+ <div
+ className="settings-content"
+ >
+ <div
+ className="settings-logo"
+ >
+ <SonarCloudIcon
+ size={128}
+ />
+ </div>
+ <p
+ className="settings-description"
+ >
+ <React.Fragment>
+ This repository is already bound to a
+
+ <a
+ href="/dashboard?id=foo"
+ >
+ SonarCloud project
+ </a>
+ </React.Fragment>
+ </p>
+ <React.Fragment>
+ <form
+ className="settings-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="display-flex-justify-center"
+ >
+ <CheckboxStateless
+ isChecked={true}
+ label="Show repository overview widget"
+ name="show-widget"
+ onChange={[Function]}
+ value="show-widget"
+ />
+ </div>
+ <div
+ className="ak-field-group"
+ >
+ <WithAnalyticsContext(WithAnalyticsEvents(WithDeprecationWarnings(Button)))
+ analyticsContext={Object {}}
+ appearance="primary"
+ isDisabled={true}
+ type="submit"
+ >
+ Save
+ </WithAnalyticsContext(WithAnalyticsEvents(WithDeprecationWarnings(Button)))>
+ </div>
+ </form>
+ <HelpLink />
+ </React.Fragment>
+ </div>
+</React.Fragment>
+`;
+
+exports[`should display correctly for auto binding with already a projectKey 1`] = `
+<React.Fragment>
+ <h2>
+ SonarCloud Settings
+ </h2>
+ <div
+ className="settings-content"
+ >
+ <div
+ className="settings-logo"
+ >
+ <SonarCloudIcon
+ size={128}
+ />
+ </div>
+ <p
+ className="settings-description"
+ >
+ <React.Fragment>
+ This repository is already bound to a
+
+ <a
+ href="/dashboard?id=foo"
+ >
+ SonarCloud project
+ </a>
+ </React.Fragment>
+ </p>
+ <React.Fragment>
+ <form
+ className="settings-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="display-flex-justify-center"
+ >
+ <CheckboxStateless
+ isChecked={true}
+ label="Show repository overview widget"
+ name="show-widget"
+ onChange={[Function]}
+ value="show-widget"
+ />
+ </div>
+ <div
+ className="ak-field-group"
+ >
+ <WithAnalyticsContext(WithAnalyticsEvents(WithDeprecationWarnings(Button)))
+ analyticsContext={Object {}}
+ appearance="primary"
+ isDisabled={true}
+ type="submit"
+ >
+ Save
+ </WithAnalyticsContext(WithAnalyticsEvents(WithDeprecationWarnings(Button)))>
+ </div>
+ </form>
+ <HelpLink />
+ </React.Fragment>
+ </div>
+</React.Fragment>
+`;
+
exports[`should display the authentication component and the display checkbox 1`] = `
<form
className="settings-form"
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should display correctly 1`] = `
+exports[`should display a link to SonarCloud for auto provisioning 1`] = `
+<React.Fragment>
+ <div
+ className="project-card-header"
+ >
+ <div
+ className="project-card-header-inner"
+ >
+ <SonarCloudIcon
+ size={24}
+ />
+ <h4
+ className="spacer-left"
+ >
+ Code Quality
+ </h4>
+ </div>
+ </div>
+ <div
+ className="spacer-top"
+ >
+ <React.Fragment>
+ You have to
+ <a
+ href="/projects/create"
+ >
+ provision
+ </a>
+ a project in SonarCloud for this repository.
+ </React.Fragment>
+ </div>
+</React.Fragment>
+`;
+
+exports[`should display correctly for admin 1`] = `
<React.Fragment>
<div
className="project-card-header"
</React.Fragment>
`;
-exports[`should display correctly 2`] = `
+exports[`should display correctly for admin 2`] = `
<React.Fragment>
<div
className="project-card-header"
</div>
</React.Fragment>
`;
+
+exports[`should display correctly for non admin 1`] = `
+<React.Fragment>
+ <div
+ className="project-card-header"
+ >
+ <div
+ className="project-card-header-inner"
+ >
+ <SonarCloudIcon
+ size={24}
+ />
+ <h4
+ className="spacer-left"
+ >
+ Code Quality
+ </h4>
+ </div>
+ </div>
+ <div
+ className="spacer-top"
+ >
+ Contact a repository administrator to link the repository with a SonarCloud project.
+ </div>
+</React.Fragment>
+`;
return (window as any).projectKey;
}
+export function isManualBindingAllowed(): boolean {
+ return (window as any).allowManualBinding !== 'false';
+}
+
export function getPullRequestContext(): PullRequestContext {
return {
prId: (window as any).prId || query.prId,
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
return executeLargeInputs(repoIds, partionnedIds -> getMapper(session).selectByRepoIds(alm.getId(), partionnedIds));
}
+ public Optional<ProjectAlmBindingDto> selectByRepoId(final DbSession session, ALM alm, String repoId) {
+ return Optional.ofNullable(getMapper(session).selectByRepoId(alm.getId(), repoId));
+ }
+
private static void checkAlm(@Nullable ALM alm) {
Objects.requireNonNull(alm, "alm can't be null");
}
@Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now);
List<ProjectAlmBindingDto> selectByRepoIds(@Param("almId") String almId, @Param("repoIds") List<String> repoIds);
+
+ ProjectAlmBindingDto selectByRepoId(@Param("almId") String almId, @Param("repoId") String repoId);
}
url
from project_alm_bindings
where
- alm_id =#{almId, jdbcType=VARCHAR}
+ alm_id = #{almId, jdbcType=VARCHAR}
and repo_id in
<foreach collection="repoIds" open="(" close=")" item="repoId" separator=",">
#{repoId,jdbcType=VARCHAR}
</foreach>
</select>
+ <select id="selectByRepoId" parameterType="map" resultType="ProjectAlmBinding">
+ select
+ uuid,
+ alm_id as almId,
+ repo_id as repoId,
+ project_uuid as projectUuid,
+ github_slug as githubSlug,
+ url
+ from project_alm_bindings
+ where
+ alm_id = #{almId, jdbcType=VARCHAR}
+ and repo_id = #{repoId, jdbcType=VARCHAR}
+ </select>
+
</mapper>
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import javax.annotation.Nullable;
import org.assertj.core.api.AbstractAssert;
import org.junit.Rule;
assertThat(underTest.bindingExists(dbSession, GITHUB, A_REPO)).isTrue();
}
+ @Test
+ public void select_by_repo_id() {
+ when(system2.now()).thenReturn(DATE);
+ when(uuidFactory.create())
+ .thenReturn("uuid1")
+ .thenReturn("uuid2")
+ .thenReturn("uuid3");
+ underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+ underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
+ underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo");
+
+ assertThat(underTest.selectByRepoId(dbSession, GITHUB, "foo")).isNotPresent();
+
+ Optional<ProjectAlmBindingDto> dto = underTest.selectByRepoId(dbSession, GITHUB, A_REPO);
+ assertThat(dto).isPresent();
+ assertThat(dto.get().getUuid()).isEqualTo("uuid1");
+ assertThat(dto.get().getAlmId()).isEqualTo(GITHUB.getId());
+ assertThat(dto.get().getRepoId()).isEqualTo(A_REPO);
+ assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID);
+ assertThat(dto.get().getUrl()).isEqualTo(A_URL);
+ assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG);
+ }
+
@Test
public void select_by_repo_ids() {
when(system2.now()).thenReturn(DATE);