Browse Source

SONAR-19454 New code definition is made part of Gitlab project onboarding

tags/10.1.0.73491
Philippe Perrin 11 months ago
parent
commit
546ea739e4

+ 8
- 5
server/sonar-web/src/main/js/api/alm-integrations.ts View File

@@ -250,13 +250,16 @@ export function getGitlabProjects(data: {
.catch(throwGlobalError);
}

export function setupGitlabProjectCreation(data: { almSetting: string; gitlabProjectId: string }) {
return (newCodeDefinitionType?: string, newCodeDefinitionValue?: string) =>
importGitlabProject({ ...data, newCodeDefinitionType, newCodeDefinitionValue });
}

export function importGitlabProject(data: {
almSetting: string;
gitlabProjectId: string;
newCodeDefinitionType?: string;
newCodeDefinitionValue?: string;
}): Promise<{ project: ProjectBase }> {
const { almSetting, gitlabProjectId } = data;
return postJSON('/api/alm_integrations/import_gitlab_project', {
almSetting,
gitlabProjectId,
}).catch(throwGlobalError);
return postJSON('/api/alm_integrations/import_gitlab_project', data).catch(throwGlobalError);
}

+ 2
- 0
server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts View File

@@ -60,6 +60,7 @@ import {
setupAzureProjectCreation,
setupBitbucketCloudProjectCreation,
setupBitbucketServerProjectCreation,
setupGitlabProjectCreation,
} from '../alm-integrations';

export default class AlmIntegrationsServiceMock {
@@ -187,6 +188,7 @@ export default class AlmIntegrationsServiceMock {
.mockImplementation(this.checkPersonalAccessTokenIsValid);
jest.mocked(setAlmPersonalAccessToken).mockImplementation(this.setAlmPersonalAccessToken);
jest.mocked(getGitlabProjects).mockImplementation(this.getGitlabProjects);
jest.mocked(setupGitlabProjectCreation).mockReturnValue(() => this.importProject());
jest.mocked(importGitlabProject).mockImplementation(this.importProject);
jest.mocked(setupBitbucketCloudProjectCreation).mockReturnValue(() => this.importProject());
jest.mocked(importBitbucketCloudRepository).mockImplementation(this.importProject);

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx View File

@@ -255,9 +255,9 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp
canAdmin={!!canAdmin}
loadingBindings={loading}
location={location}
onProjectCreate={this.handleProjectCreate}
router={router}
almInstances={gitlabSettings}
onProjectSetupDone={this.handleProjectSetupDone}
/>
);
}

+ 8
- 31
server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx View File

@@ -18,24 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { getGitlabProjects, importGitlabProject } from '../../../../api/alm-integrations';
import { getGitlabProjects, setupGitlabProjectCreation } from '../../../../api/alm-integrations';
import { Location, Router } from '../../../../components/hoc/withRouter';
import { GitlabProject } from '../../../../types/alm-integration';
import { AlmSettingsInstance } from '../../../../types/alm-settings';
import { Paging } from '../../../../types/types';
import { CreateProjectApiCallback } from '../types';
import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer';

interface Props {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKey: string) => void;
almInstances: AlmSettingsInstance[];
location: Location;
router: Router;
onProjectSetupDone: (createProject: CreateProjectApiCallback) => void;
}

interface State {
importingGitlabProjectId?: string;
loading: boolean;
loadingMore: boolean;
projects?: GitlabProject[];
@@ -134,34 +134,13 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
}
};

doImport = async (gitlabProjectId: string) => {
handleImport = (gitlabProjectId: string) => {
const { selectedAlmInstance } = this.state;

if (!selectedAlmInstance) {
return Promise.resolve(undefined);
}

try {
return await importGitlabProject({
almSetting: selectedAlmInstance.key,
gitlabProjectId,
});
} catch (_) {
return this.handleError();
}
};

handleImport = async (gitlabProjectId: string) => {
this.setState({ importingGitlabProjectId: gitlabProjectId });

const result = await this.doImport(gitlabProjectId);

if (this.mounted) {
this.setState({ importingGitlabProjectId: undefined });

if (result) {
this.props.onProjectCreate(result.project.key);
}
if (selectedAlmInstance) {
this.props.onProjectSetupDone(
setupGitlabProjectCreation({ almSetting: selectedAlmInstance.key, gitlabProjectId })
);
}
};

@@ -221,7 +200,6 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
render() {
const { loadingBindings, location, almInstances, canAdmin } = this.props;
const {
importingGitlabProjectId,
loading,
loadingMore,
projects,
@@ -238,7 +216,6 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat
canAdmin={canAdmin}
almInstances={almInstances}
selectedAlmInstance={selectedAlmInstance}
importingGitlabProjectId={importingGitlabProjectId}
loading={loading || loadingBindings}
loadingMore={loadingMore}
onImport={this.handleImport}

+ 0
- 3
server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx View File

@@ -31,7 +31,6 @@ import GitlabProjectSelectionForm from './GitlabProjectSelectionForm';

export interface GitlabProjectCreateRendererProps {
canAdmin?: boolean;
importingGitlabProjectId?: string;
loading: boolean;
loadingMore: boolean;
onImport: (gitlabProjectId: string) => void;
@@ -52,7 +51,6 @@ export interface GitlabProjectCreateRendererProps {
export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) {
const {
canAdmin,
importingGitlabProjectId,
loading,
loadingMore,
projects,
@@ -104,7 +102,6 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe
/>
) : (
<GitlabProjectSelectionForm
importingGitlabProjectId={importingGitlabProjectId}
loadingMore={loadingMore}
onImport={props.onImport}
onLoadMore={props.onLoadMore}

+ 73
- 93
server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx View File

@@ -27,17 +27,14 @@ import { Button } from '../../../../components/controls/buttons';
import CheckIcon from '../../../../components/icons/CheckIcon';
import QualifierIcon from '../../../../components/icons/QualifierIcon';
import { Alert } from '../../../../components/ui/Alert';
import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
import { translate } from '../../../../helpers/l10n';
import { getProjectUrl, queryToSearch } from '../../../../helpers/urls';
import { GitlabProject } from '../../../../types/alm-integration';
import { ComponentQualifier } from '../../../../types/component';
import { Paging } from '../../../../types/types';
import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning';
import { CreateProjectModes } from '../types';

export interface GitlabProjectSelectionFormProps {
importingGitlabProjectId?: string;
loadingMore: boolean;
onImport: (gitlabProjectId: string) => void;
onLoadMore: () => void;
@@ -49,14 +46,7 @@ export interface GitlabProjectSelectionFormProps {
}

export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) {
const {
importingGitlabProjectId,
loadingMore,
projects = [],
projectsPaging,
searching,
searchQuery,
} = props;
const { loadingMore, projects = [], projectsPaging, searching, searchQuery } = props;

if (projects.length === 0 && searchQuery.length === 0 && !searching) {
return (
@@ -82,92 +72,82 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection
}

return (
<>
<InstanceNewCodeDefinitionComplianceWarning />
<div className="boxed-group big-padded create-project-import">
<SearchBox
className="spacer"
loading={searching}
minLength={3}
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.search_prompt')}
/>
<div className="boxed-group big-padded create-project-import">
<SearchBox
className="spacer"
loading={searching}
minLength={3}
onChange={props.onSearch}
placeholder={translate('onboarding.create_project.search_prompt')}
/>

<hr />
<hr />

{projects.length === 0 ? (
<div className="padded">{translate('no_results')}</div>
) : (
<table className="data zebra zebra-hover">
<tbody>
{projects.map((project) => (
<tr key={project.id}>
{projects.length === 0 ? (
<div className="padded">{translate('no_results')}</div>
) : (
<table className="data zebra zebra-hover">
<tbody>
{projects.map((project) => (
<tr key={project.id}>
<td>
<Tooltip overlay={project.slug}>
<strong className="project-name display-inline-block text-ellipsis">
{project.sqProjectKey ? (
<Link to={getProjectUrl(project.sqProjectKey)}>
<QualifierIcon
className="spacer-right"
qualifier={ComponentQualifier.Project}
/>
{project.sqProjectName}
</Link>
) : (
project.name
)}
</strong>
</Tooltip>
<br />
<Tooltip overlay={project.pathSlug}>
<span className="text-muted project-path display-inline-block text-ellipsis">
{project.pathName}
</span>
</Tooltip>
</td>
<td>
<Link
className="display-inline-flex-center big-spacer-right"
to={project.url}
target="_blank"
>
{translate('onboarding.create_project.gitlab.link')}
</Link>
</td>
{project.sqProjectKey ? (
<td>
<Tooltip overlay={project.slug}>
<strong className="project-name display-inline-block text-ellipsis">
{project.sqProjectKey ? (
<Link to={getProjectUrl(project.sqProjectKey)}>
<QualifierIcon
className="spacer-right"
qualifier={ComponentQualifier.Project}
/>
{project.sqProjectName}
</Link>
) : (
project.name
)}
</strong>
</Tooltip>
<br />
<Tooltip overlay={project.pathSlug}>
<span className="text-muted project-path display-inline-block text-ellipsis">
{project.pathName}
</span>
</Tooltip>
<span className="display-flex-center display-flex-justify-end already-set-up">
<CheckIcon className="little-spacer-right" size={12} />
{translate('onboarding.create_project.repository_imported')}
</span>
</td>
<td>
<Link
className="display-inline-flex-center big-spacer-right"
to={project.url}
target="_blank"
>
{translate('onboarding.create_project.gitlab.link')}
</Link>
) : (
<td className="text-right">
<Button onClick={() => props.onImport(project.id)}>
{translate('onboarding.create_project.set_up')}
</Button>
</td>
{project.sqProjectKey ? (
<td>
<span className="display-flex-center display-flex-justify-end already-set-up">
<CheckIcon className="little-spacer-right" size={12} />
{translate('onboarding.create_project.repository_imported')}
</span>
</td>
) : (
<td className="text-right">
<Button
disabled={!!importingGitlabProjectId}
onClick={() => props.onImport(project.id)}
>
{translate('onboarding.create_project.set_up')}
<DeferredSpinner
className="spacer-left"
loading={importingGitlabProjectId === project.id}
/>
</Button>
</td>
)}
</tr>
))}
</tbody>
</table>
)}
<ListFooter
count={projects.length}
loadMore={props.onLoadMore}
loading={loadingMore}
pageSize={projectsPaging.pageSize}
total={projectsPaging.total}
/>
</div>
</>
)}
</tr>
))}
</tbody>
</table>
)}
<ListFooter
count={projects.length}
loadMore={props.onLoadMore}
loading={loadingMore}
pageSize={projectsPaging.pageSize}
total={projectsPaging.total}
/>
</div>
);
}

+ 15
- 27
server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx View File

@@ -26,9 +26,7 @@ import { getGitlabProjects } from '../../../../api/alm-integrations';
import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock';
import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock';
import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock';
import { mockNewCodePeriod } from '../../../../helpers/mocks/new-code-period';
import { renderApp } from '../../../../helpers/testReactTestingUtils';
import { NewCodePeriodSettingType } from '../../../../types/types';
import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage';

jest.mock('../../../../api/alm-integrations');
@@ -108,16 +106,24 @@ it('should show import project feature when PAT is already set', async () => {
'/dashboard?id=key'
);

projectItem = screen.getByRole('row', {
name: 'Gitlab project 2 Company / Best Projects opens_in_new_window onboarding.create_project.gitlab.link onboarding.create_project.set_up',
});
const importProjectButton = within(projectItem).getByRole('button', {
projectItem = screen.getByRole('row', { name: /Gitlab project 2/ });
const setupButton = within(projectItem).getByRole('button', {
name: 'onboarding.create_project.set_up',
});

await act(async () => {
await user.click(importProjectButton);
});
await user.click(setupButton);

expect(
screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' })
).toBeInTheDocument();

await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' }));
await user.click(
screen.getByRole('button', {
name: 'onboarding.create_project.new_code_definition.create_project',
})
);

expect(await screen.findByText('/dashboard?id=key')).toBeInTheDocument();
});

@@ -182,24 +188,6 @@ it('should show no result message when there are no projects', async () => {
expect(screen.getByText('onboarding.create_project.gitlab.no_projects')).toBeInTheDocument();
});

it('should display a warning if the instance default new code definition is not CaYC compliant', async () => {
const user = userEvent.setup();
newCodePeriodHandler.setNewCodePeriod(
mockNewCodePeriod({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '91' })
);
renderCreateProject();
await act(async () => {
await user.click(ui.gitlabCreateProjectButton.get());
await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]);
});

expect(screen.getByText('Gitlab project 1')).toBeInTheDocument();
expect(screen.getByText('Gitlab project 2')).toBeInTheDocument();
expect(
screen.getByText('onboarding.create_project.new_code_option.warning.title')
).toBeInTheDocument();
});

function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) {
renderApp('project/create', <CreateProjectPage {...props} />);
}

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/NewCodeDefinitionResolver.java View File

@@ -43,7 +43,7 @@ public class NewCodeDefinitionResolver {
private static final String BEGIN_ITEM_LIST = "<li>";
private static final String END_ITEM_LIST = "</li>";

public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Type<br/>" +
public static final String NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Type<br/>" +
"New code definitions of the following types are allowed:" +
BEGIN_LIST +
BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + END_ITEM_LIST +
@@ -51,7 +51,7 @@ public class NewCodeDefinitionResolver {
BEGIN_ITEM_LIST + REFERENCE_BRANCH.name() + " - will default to the main branch." + END_ITEM_LIST +
END_LIST;

public static final String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Value<br/>" +
public static final String NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION = "Project New Code Definition Value<br/>" +
"For each new code definition type, a different value is expected:" +
BEGIN_LIST +
BEGIN_ITEM_LIST + "no value, when the new code definition type is " + PREVIOUS_VERSION.name() + " and " + REFERENCE_BRANCH.name() + END_ITEM_LIST +

Loading…
Cancel
Save