@@ -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); | |||
} |
@@ -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); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} |
@@ -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} |
@@ -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> | |||
); | |||
} |
@@ -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} />); | |||
} |
@@ -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 + |