import { GitlabProject } from '../../types/alm-integration'; | import { GitlabProject } from '../../types/alm-integration'; | ||||
import { | import { | ||||
checkPersonalAccessTokenIsValid, | checkPersonalAccessTokenIsValid, | ||||
getGithubClientId, | |||||
getGithubOrganizations, | |||||
getGitlabProjects, | getGitlabProjects, | ||||
setAlmPersonalAccessToken, | setAlmPersonalAccessToken, | ||||
} from '../alm-integrations'; | } from '../alm-integrations'; | ||||
defaultAlmInstancePATMap: { [key: string]: boolean } = { | defaultAlmInstancePATMap: { [key: string]: boolean } = { | ||||
'conf-final-1': false, | 'conf-final-1': false, | ||||
'conf-final-2': true, | 'conf-final-2': true, | ||||
'conf-github-1': false, | |||||
'conf-github-2': true, | |||||
}; | }; | ||||
defaultGitlabProjects: GitlabProject[] = [ | defaultGitlabProjects: GitlabProject[] = [ | ||||
mockGitlabProject({ name: 'Gitlab project 3', id: '3' }), | mockGitlabProject({ name: 'Gitlab project 3', id: '3' }), | ||||
]; | ]; | ||||
defaultOrganizations = { | |||||
paging: { | |||||
pageIndex: 1, | |||||
pageSize: 100, | |||||
total: 1, | |||||
}, | |||||
organizations: [ | |||||
{ | |||||
key: 'org-1', | |||||
name: 'org-1', | |||||
}, | |||||
], | |||||
}; | |||||
constructor() { | constructor() { | ||||
this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | ||||
this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); | this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); | ||||
); | ); | ||||
(setAlmPersonalAccessToken as jest.Mock).mockImplementation(this.setAlmPersonalAccessToken); | (setAlmPersonalAccessToken as jest.Mock).mockImplementation(this.setAlmPersonalAccessToken); | ||||
(getGitlabProjects as jest.Mock).mockImplementation(this.getGitlabProjects); | (getGitlabProjects as jest.Mock).mockImplementation(this.getGitlabProjects); | ||||
(getGithubClientId as jest.Mock).mockImplementation(this.getGithubClientId); | |||||
(getGithubOrganizations as jest.Mock).mockImplementation(this.getGithubOrganizations); | |||||
} | } | ||||
checkPersonalAccessTokenIsValid = (conf: string) => { | checkPersonalAccessTokenIsValid = (conf: string) => { | ||||
this.gitlabProjects = gitlabProjects; | this.gitlabProjects = gitlabProjects; | ||||
} | } | ||||
getGithubClientId = () => { | |||||
return Promise.resolve({ clientId: 'clientId' }); | |||||
}; | |||||
getGithubOrganizations = () => { | |||||
return Promise.resolve(this.defaultOrganizations); | |||||
}; | |||||
reset = () => { | reset = () => { | ||||
this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | ||||
this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); | this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); |
defaultSetting: AlmSettingsInstance[] = [ | defaultSetting: AlmSettingsInstance[] = [ | ||||
mockAlmSettingsInstance({ key: 'conf-final-1', alm: AlmKeys.GitLab }), | mockAlmSettingsInstance({ key: 'conf-final-1', alm: AlmKeys.GitLab }), | ||||
mockAlmSettingsInstance({ key: 'conf-final-2', alm: AlmKeys.GitLab }), | mockAlmSettingsInstance({ key: 'conf-final-2', alm: AlmKeys.GitLab }), | ||||
mockAlmSettingsInstance({ key: 'conf-github-1', alm: AlmKeys.GitHub, url: 'url' }), | |||||
mockAlmSettingsInstance({ key: 'conf-github-2', alm: AlmKeys.GitHub, url: 'url' }), | |||||
]; | ]; | ||||
constructor() { | constructor() { |
} | } | ||||
}; | }; | ||||
onChangeConfig = (instance: AlmSettingsInstance) => { | |||||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||||
this.setState({ selectedAlmInstance: instance }, () => this.fetchData()); | this.setState({ selectedAlmInstance: instance }, () => this.fetchData()); | ||||
}; | }; | ||||
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)} | showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)} | ||||
submittingToken={submittingToken} | submittingToken={submittingToken} | ||||
tokenValidationFailed={tokenValidationFailed} | tokenValidationFailed={tokenValidationFailed} | ||||
onChangeConfig={this.onChangeConfig} | |||||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
showPersonalAccessTokenForm?: boolean; | showPersonalAccessTokenForm?: boolean; | ||||
submittingToken?: boolean; | submittingToken?: boolean; | ||||
tokenValidationFailed: boolean; | tokenValidationFailed: boolean; | ||||
onChangeConfig: (instance: AlmSettingsInstance) => void; | |||||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||||
} | } | ||||
export default function AzureProjectCreateRenderer(props: AzureProjectCreateRendererProps) { | export default function AzureProjectCreateRenderer(props: AzureProjectCreateRendererProps) { | ||||
<AlmSettingsInstanceDropdown | <AlmSettingsInstanceDropdown | ||||
almInstances={almInstances} | almInstances={almInstances} | ||||
selectedAlmInstance={selectedAlmInstance} | selectedAlmInstance={selectedAlmInstance} | ||||
onChangeConfig={props.onChangeConfig} | |||||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||||
/> | /> | ||||
{loading && <i className="spinner" />} | {loading && <i className="spinner" />} |
import './style.css'; | import './style.css'; | ||||
import { CreateProjectModes } from './types'; | import { CreateProjectModes } from './types'; | ||||
interface Props extends WithAvailableFeaturesProps { | |||||
export interface CreateProjectPageProps extends WithAvailableFeaturesProps { | |||||
appState: AppState; | appState: AppState; | ||||
location: Location; | location: Location; | ||||
router: Router; | router: Router; | ||||
[AlmKeys.GitLab]: CreateProjectModes.GitLab, | [AlmKeys.GitLab]: CreateProjectModes.GitLab, | ||||
}; | }; | ||||
export class CreateProjectPage extends React.PureComponent<Props, State> { | |||||
export class CreateProjectPage extends React.PureComponent<CreateProjectPageProps, State> { | |||||
mounted = false; | mounted = false; | ||||
state: State = { | state: State = { | ||||
azureSettings: [], | azureSettings: [], | ||||
location={location} | location={location} | ||||
onProjectCreate={this.handleProjectCreate} | onProjectCreate={this.handleProjectCreate} | ||||
router={router} | router={router} | ||||
settings={githubSettings} | |||||
almInstances={githubSettings} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
canAdmin: boolean; | canAdmin: boolean; | ||||
loadingBindings: boolean; | loadingBindings: boolean; | ||||
onProjectCreate: (projectKey: string) => void; | onProjectCreate: (projectKey: string) => void; | ||||
settings: AlmSettingsInstance[]; | |||||
almInstances: AlmSettingsInstance[]; | |||||
location: Location; | location: Location; | ||||
router: Router; | router: Router; | ||||
} | } | ||||
searchQuery: string; | searchQuery: string; | ||||
selectedOrganization?: GithubOrganization; | selectedOrganization?: GithubOrganization; | ||||
selectedRepository?: GithubRepository; | selectedRepository?: GithubRepository; | ||||
settings?: AlmSettingsInstance; | |||||
selectedAlmInstance?: AlmSettingsInstance; | |||||
} | } | ||||
const REPOSITORY_PAGE_SIZE = 30; | const REPOSITORY_PAGE_SIZE = 30; | ||||
repositories: [], | repositories: [], | ||||
repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 }, | repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 }, | ||||
searchQuery: '', | searchQuery: '', | ||||
settings: props.settings[0], | |||||
selectedAlmInstance: this.getInitialSelectedAlmInstance(), | |||||
}; | }; | ||||
this.triggerSearch = debounce(this.triggerSearch, 250); | this.triggerSearch = debounce(this.triggerSearch, 250); | ||||
componentDidMount() { | componentDidMount() { | ||||
this.mounted = true; | this.mounted = true; | ||||
this.initialize(); | this.initialize(); | ||||
} | } | ||||
componentDidUpdate(prevProps: Props) { | componentDidUpdate(prevProps: Props) { | ||||
if (prevProps.settings.length === 0 && this.props.settings.length > 0) { | |||||
this.setState({ settings: this.props.settings[0] }, () => this.initialize()); | |||||
if (prevProps.almInstances.length === 0 && this.props.almInstances.length > 0) { | |||||
this.setState({ selectedAlmInstance: this.getInitialSelectedAlmInstance() }, () => | |||||
this.initialize() | |||||
); | |||||
} | } | ||||
} | } | ||||
this.mounted = false; | this.mounted = false; | ||||
} | } | ||||
getInitialSelectedAlmInstance() { | |||||
const { | |||||
location: { | |||||
query: { almInstance: selectedAlmInstanceKey }, | |||||
}, | |||||
almInstances, | |||||
} = this.props; | |||||
const selectedAlmInstance = almInstances.find( | |||||
(instance) => instance.key === selectedAlmInstanceKey | |||||
); | |||||
if (selectedAlmInstance) { | |||||
return selectedAlmInstance; | |||||
} | |||||
return this.props.almInstances.length > 1 ? undefined : this.props.almInstances[0]; | |||||
} | |||||
async initialize() { | async initialize() { | ||||
const { location, router } = this.props; | const { location, router } = this.props; | ||||
const { settings } = this.state; | |||||
if (!settings || !settings.url) { | |||||
const { selectedAlmInstance } = this.state; | |||||
if (!selectedAlmInstance || !selectedAlmInstance.url) { | |||||
this.setState({ error: true }); | this.setState({ error: true }); | ||||
return; | return; | ||||
} else { | |||||
this.setState({ error: false }); | |||||
} | } | ||||
this.setState({ error: false }); | |||||
const code = location.query?.code; | const code = location.query?.code; | ||||
try { | try { | ||||
if (!code) { | if (!code) { | ||||
await this.redirectToGithub(settings); | |||||
await this.redirectToGithub(selectedAlmInstance); | |||||
} else { | } else { | ||||
delete location.query.code; | delete location.query.code; | ||||
router.replace(location); | router.replace(location); | ||||
await this.fetchOrganizations(settings, code); | |||||
await this.fetchOrganizations(selectedAlmInstance, code); | |||||
} | } | ||||
} catch (e) { | } catch (e) { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
} | } | ||||
} | } | ||||
async redirectToGithub(settings: AlmSettingsInstance) { | |||||
if (!settings.url) { | |||||
async redirectToGithub(selectedAlmInstance: AlmSettingsInstance) { | |||||
if (!selectedAlmInstance.url) { | |||||
return; | return; | ||||
} | } | ||||
const { clientId } = await getGithubClientId(settings.key); | |||||
const { clientId } = await getGithubClientId(selectedAlmInstance.key); | |||||
if (!clientId) { | if (!clientId) { | ||||
this.setState({ error: true }); | this.setState({ error: true }); | ||||
return; | return; | ||||
} | } | ||||
const queryParams = [ | const queryParams = [ | ||||
{ param: 'client_id', value: clientId }, | { param: 'client_id', value: clientId }, | ||||
{ param: 'redirect_uri', value: `${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}` }, | |||||
{ | |||||
param: 'redirect_uri', | |||||
value: encodeURIComponent( | |||||
`${getHostUrl()}/projects/create?mode=${AlmKeys.GitHub}&almInstance=${ | |||||
selectedAlmInstance.key | |||||
}` | |||||
), | |||||
}, | |||||
] | ] | ||||
.map(({ param, value }) => `${param}=${value}`) | .map(({ param, value }) => `${param}=${value}`) | ||||
.join('&'); | .join('&'); | ||||
let instanceRootUrl; | let instanceRootUrl; | ||||
// Strip the api section from the url, since we're not hitting the api here. | // Strip the api section from the url, since we're not hitting the api here. | ||||
if (settings.url.includes('/api/v3')) { | |||||
if (selectedAlmInstance.url.includes('/api/v3')) { | |||||
// GitHub Enterprise | // GitHub Enterprise | ||||
instanceRootUrl = settings.url.replace('/api/v3', ''); | |||||
instanceRootUrl = selectedAlmInstance.url.replace('/api/v3', ''); | |||||
} else { | } else { | ||||
// github.com | // github.com | ||||
instanceRootUrl = settings.url.replace('api.', ''); | |||||
instanceRootUrl = selectedAlmInstance.url.replace('api.', ''); | |||||
} | } | ||||
// strip the trailing / | // strip the trailing / | ||||
window.location.replace(`${instanceRootUrl}/login/oauth/authorize?${queryParams}`); | window.location.replace(`${instanceRootUrl}/login/oauth/authorize?${queryParams}`); | ||||
} | } | ||||
async fetchOrganizations(settings: AlmSettingsInstance, token: string) { | |||||
const { organizations } = await getGithubOrganizations(settings.key, token); | |||||
async fetchOrganizations(selectedAlmInstance: AlmSettingsInstance, token: string) { | |||||
const { organizations } = await getGithubOrganizations(selectedAlmInstance.key, token); | |||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState({ loadingOrganizations: false, organizations }); | this.setState({ loadingOrganizations: false, organizations }); | ||||
async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) { | async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) { | ||||
const { organizationKey, page = 1, query } = params; | const { organizationKey, page = 1, query } = params; | ||||
const { settings } = this.state; | |||||
const { selectedAlmInstance } = this.state; | |||||
if (!settings) { | |||||
if (!selectedAlmInstance) { | |||||
this.setState({ error: true }); | this.setState({ error: true }); | ||||
return; | return; | ||||
} | } | ||||
try { | try { | ||||
const data = await getGithubRepositories({ | const data = await getGithubRepositories({ | ||||
almSetting: settings.key, | |||||
almSetting: selectedAlmInstance.key, | |||||
organization: organizationKey, | organization: organizationKey, | ||||
pageSize: REPOSITORY_PAGE_SIZE, | pageSize: REPOSITORY_PAGE_SIZE, | ||||
page, | page, | ||||
}; | }; | ||||
handleImportRepository = async () => { | handleImportRepository = async () => { | ||||
const { selectedOrganization, selectedRepository, settings } = this.state; | |||||
const { selectedOrganization, selectedRepository, selectedAlmInstance } = this.state; | |||||
if (settings && selectedOrganization && selectedRepository) { | |||||
if (selectedAlmInstance && selectedOrganization && selectedRepository) { | |||||
this.setState({ importing: true }); | this.setState({ importing: true }); | ||||
try { | try { | ||||
const { project } = await importGithubRepository( | const { project } = await importGithubRepository( | ||||
settings.key, | |||||
selectedAlmInstance.key, | |||||
selectedOrganization.key, | selectedOrganization.key, | ||||
selectedRepository.key | selectedRepository.key | ||||
); | ); | ||||
} | } | ||||
}; | }; | ||||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||||
this.setState({ selectedAlmInstance: instance }, () => this.initialize()); | |||||
}; | |||||
render() { | render() { | ||||
const { canAdmin, loadingBindings } = this.props; | |||||
const { canAdmin, loadingBindings, almInstances } = this.props; | |||||
const { | const { | ||||
error, | error, | ||||
importing, | importing, | ||||
searchQuery, | searchQuery, | ||||
selectedOrganization, | selectedOrganization, | ||||
selectedRepository, | selectedRepository, | ||||
selectedAlmInstance, | |||||
} = this.state; | } = this.state; | ||||
return ( | return ( | ||||
repositories={repositories} | repositories={repositories} | ||||
selectedOrganization={selectedOrganization} | selectedOrganization={selectedOrganization} | ||||
selectedRepository={selectedRepository} | selectedRepository={selectedRepository} | ||||
almInstances={almInstances} | |||||
selectedAlmInstance={selectedAlmInstance} | |||||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
import { getBaseUrl } from '../../../helpers/system'; | import { getBaseUrl } from '../../../helpers/system'; | ||||
import { getProjectUrl } from '../../../helpers/urls'; | import { getProjectUrl } from '../../../helpers/urls'; | ||||
import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; | import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; | ||||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||||
import { ComponentQualifier } from '../../../types/component'; | import { ComponentQualifier } from '../../../types/component'; | ||||
import { Paging } from '../../../types/types'; | import { Paging } from '../../../types/types'; | ||||
import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; | |||||
import CreateProjectPageHeader from './CreateProjectPageHeader'; | import CreateProjectPageHeader from './CreateProjectPageHeader'; | ||||
export interface GitHubProjectCreateRendererProps { | export interface GitHubProjectCreateRendererProps { | ||||
searchQuery: string; | searchQuery: string; | ||||
selectedOrganization?: GithubOrganization; | selectedOrganization?: GithubOrganization; | ||||
selectedRepository?: GithubRepository; | selectedRepository?: GithubRepository; | ||||
almInstances: AlmSettingsInstance[]; | |||||
selectedAlmInstance?: AlmSettingsInstance; | |||||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||||
} | } | ||||
function orgToOption({ key, name }: GithubOrganization) { | function orgToOption({ key, name }: GithubOrganization) { | ||||
organizations, | organizations, | ||||
selectedOrganization, | selectedOrganization, | ||||
selectedRepository, | selectedRepository, | ||||
almInstances, | |||||
selectedAlmInstance, | |||||
} = props; | } = props; | ||||
if (loadingBindings) { | if (loadingBindings) { | ||||
} | } | ||||
/> | /> | ||||
{error ? ( | |||||
<AlmSettingsInstanceDropdown | |||||
almInstances={almInstances} | |||||
selectedAlmInstance={selectedAlmInstance} | |||||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||||
/> | |||||
{error && selectedAlmInstance && ( | |||||
<div className="display-flex-justify-center"> | <div className="display-flex-justify-center"> | ||||
<div className="boxed-group padded width-50 huge-spacer-top"> | <div className="boxed-group padded width-50 huge-spacer-top"> | ||||
<h2 className="big-spacer-bottom"> | <h2 className="big-spacer-bottom"> | ||||
</Alert> | </Alert> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
) : ( | |||||
)} | |||||
{!error && ( | |||||
<DeferredSpinner loading={loadingOrganizations}> | <DeferredSpinner loading={loadingOrganizations}> | ||||
<div className="form-field"> | <div className="form-field"> | ||||
<label>{translate('onboarding.create_project.github.choose_organization')}</label> | <label>{translate('onboarding.create_project.github.choose_organization')}</label> |
await this.fetchInitialData(); | await this.fetchInitialData(); | ||||
}; | }; | ||||
onChangeConfig = (instance: AlmSettingsInstance) => { | |||||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||||
this.setState({ | this.setState({ | ||||
selectedAlmInstance: instance, | selectedAlmInstance: instance, | ||||
showPersonalAccessTokenForm: true, | showPersonalAccessTokenForm: true, | ||||
showPersonalAccessTokenForm={ | showPersonalAccessTokenForm={ | ||||
showPersonalAccessTokenForm || Boolean(location.query.resetPat) | showPersonalAccessTokenForm || Boolean(location.query.resetPat) | ||||
} | } | ||||
onChangeConfig={this.onChangeConfig} | |||||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||||
/> | /> | ||||
); | ); | ||||
} | } |
almInstances?: AlmSettingsInstance[]; | almInstances?: AlmSettingsInstance[]; | ||||
selectedAlmInstance?: AlmSettingsInstance; | selectedAlmInstance?: AlmSettingsInstance; | ||||
showPersonalAccessTokenForm?: boolean; | showPersonalAccessTokenForm?: boolean; | ||||
onChangeConfig: (instance: AlmSettingsInstance) => void; | |||||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||||
} | } | ||||
export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { | export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { | ||||
<AlmSettingsInstanceDropdown | <AlmSettingsInstanceDropdown | ||||
almInstances={almInstances} | almInstances={almInstances} | ||||
selectedAlmInstance={selectedAlmInstance} | selectedAlmInstance={selectedAlmInstance} | ||||
onChangeConfig={props.onChangeConfig} | |||||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||||
/> | /> | ||||
{loading && <i className="spinner" />} | {loading && <i className="spinner" />} |
]} | ]} | ||||
showPersonalAccessTokenForm={false} | showPersonalAccessTokenForm={false} | ||||
submittingToken={false} | submittingToken={false} | ||||
onChangeConfig={jest.fn()} | |||||
onSelectedAlmInstanceChange={jest.fn()} | |||||
{...overrides} | {...overrides} | ||||
/> | /> | ||||
); | ); |
import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; | import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; | ||||
import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; | import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; | ||||
import { renderApp } from '../../../../helpers/testReactTestingUtils'; | import { renderApp } from '../../../../helpers/testReactTestingUtils'; | ||||
import CreateProjectPage from '../CreateProjectPage'; | |||||
import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; | |||||
jest.mock('../../../../api/alm-integrations'); | jest.mock('../../../../api/alm-integrations'); | ||||
jest.mock('../../../../api/alm-settings'); | jest.mock('../../../../api/alm-settings'); | ||||
const original = window.location; | |||||
let almIntegrationHandler: AlmIntegrationsServiceMock; | let almIntegrationHandler: AlmIntegrationsServiceMock; | ||||
let almSettingsHandler: AlmSettingsServiceMock; | let almSettingsHandler: AlmSettingsServiceMock; | ||||
const ui = { | const ui = { | ||||
gitlabCreateProjectButton: byText('onboarding.create_project.select_method.gitlab'), | gitlabCreateProjectButton: byText('onboarding.create_project.select_method.gitlab'), | ||||
githubCreateProjectButton: byText('onboarding.create_project.select_method.github'), | |||||
personalAccessTokenInput: byRole('textbox', { | personalAccessTokenInput: byRole('textbox', { | ||||
name: 'onboarding.create_project.enter_pat field_required', | name: 'onboarding.create_project.enter_pat field_required', | ||||
}), | }), | ||||
}; | }; | ||||
beforeAll(() => { | beforeAll(() => { | ||||
Object.defineProperty(window, 'location', { | |||||
configurable: true, | |||||
value: { replace: jest.fn() }, | |||||
}); | |||||
almIntegrationHandler = new AlmIntegrationsServiceMock(); | almIntegrationHandler = new AlmIntegrationsServiceMock(); | ||||
almSettingsHandler = new AlmSettingsServiceMock(); | almSettingsHandler = new AlmSettingsServiceMock(); | ||||
}); | }); | ||||
almSettingsHandler.reset(); | almSettingsHandler.reset(); | ||||
}); | }); | ||||
afterAll(() => { | |||||
Object.defineProperty(window, 'location', { configurable: true, value: original }); | |||||
}); | |||||
describe('Gitlab onboarding page', () => { | describe('Gitlab onboarding page', () => { | ||||
it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { | it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { | ||||
const user = userEvent.setup(); | const user = userEvent.setup(); | ||||
}); | }); | ||||
}); | }); | ||||
function renderCreateProject() { | |||||
renderApp('project/create', <CreateProjectPage />); | |||||
describe('Github onboarding page', () => { | |||||
it('should redirect to github authorization page when not already authorized', async () => { | |||||
const user = userEvent.setup(); | |||||
renderCreateProject(); | |||||
expect(ui.githubCreateProjectButton.get()).toBeInTheDocument(); | |||||
await user.click(ui.githubCreateProjectButton.get()); | |||||
expect(screen.getByText('onboarding.create_project.github.title')).toBeInTheDocument(); | |||||
expect(screen.getByText('alm.configuration.selector.placeholder')).toBeInTheDocument(); | |||||
expect(screen.getByText('alm.configuration.selector.label')).toBeInTheDocument(); | |||||
await selectEvent.select(screen.getByLabelText('alm.configuration.selector.label'), [ | |||||
/conf-github-1/, | |||||
]); | |||||
expect(window.location.replace).toHaveBeenCalled(); | |||||
expect( | |||||
screen.getByText('onboarding.create_project.github.choose_organization') | |||||
).toBeInTheDocument(); | |||||
}); | |||||
}); | |||||
function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) { | |||||
renderApp('project/create', <CreateProjectPage {...props} />); | |||||
} | } |
}); | }); | ||||
it('should handle no settings', async () => { | it('should handle no settings', async () => { | ||||
const wrapper = shallowRender({ settings: [] }); | |||||
const wrapper = shallowRender({ almInstances: [] }); | |||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper.state().error).toBe(true); | expect(wrapper.state().error).toBe(true); | ||||
}); | }); | ||||
it('should redirect when no code - github.com', async () => { | it('should redirect when no code - github.com', async () => { | ||||
const wrapper = shallowRender({ | const wrapper = shallowRender({ | ||||
settings: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], | |||||
almInstances: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], | |||||
}); | }); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(getGithubClientId).toHaveBeenCalled(); | expect(getGithubClientId).toHaveBeenCalled(); | ||||
expect(window.location.replace).toHaveBeenCalledWith( | expect(window.location.replace).toHaveBeenCalledWith( | ||||
'github.com/login/oauth/authorize?client_id=client-id-124&redirect_uri=http://localhost/projects/create?mode=github' | |||||
'github.com/login/oauth/authorize?client_id=client-id-124&redirect_uri=http%3A%2F%2Flocalhost%2Fprojects%2Fcreate%3Fmode%3Dgithub%26almInstance%3Da' | |||||
); | ); | ||||
}); | }); | ||||
location={mockLocation()} | location={mockLocation()} | ||||
onProjectCreate={jest.fn()} | onProjectCreate={jest.fn()} | ||||
router={mockRouter()} | router={mockRouter()} | ||||
settings={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} | |||||
almInstances={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} | |||||
{...props} | {...props} | ||||
/> | /> | ||||
); | ); |
import SearchBox from '../../../../components/controls/SearchBox'; | import SearchBox from '../../../../components/controls/SearchBox'; | ||||
import Select from '../../../../components/controls/Select'; | import Select from '../../../../components/controls/Select'; | ||||
import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations'; | import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations'; | ||||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||||
import { GithubOrganization } from '../../../../types/alm-integration'; | import { GithubOrganization } from '../../../../types/alm-integration'; | ||||
import GitHubProjectCreateRenderer, { | import GitHubProjectCreateRenderer, { | ||||
GitHubProjectCreateRendererProps, | GitHubProjectCreateRendererProps, | ||||
onSearch={jest.fn()} | onSearch={jest.fn()} | ||||
onSelectOrganization={jest.fn()} | onSelectOrganization={jest.fn()} | ||||
onSelectRepository={jest.fn()} | onSelectRepository={jest.fn()} | ||||
onSelectedAlmInstanceChange={jest.fn()} | |||||
almInstances={[mockAlmSettingsInstance(), mockAlmSettingsInstance()]} | |||||
organizations={[]} | organizations={[]} | ||||
repositoryPaging={{ total: 0, pageIndex: 1, pageSize: 30 }} | repositoryPaging={{ total: 0, pageIndex: 1, pageSize: 30 }} | ||||
searchQuery="" | searchQuery="" |
onLoadMore={jest.fn()} | onLoadMore={jest.fn()} | ||||
onPersonalAccessTokenCreated={jest.fn()} | onPersonalAccessTokenCreated={jest.fn()} | ||||
onSearch={jest.fn()} | onSearch={jest.fn()} | ||||
onChangeConfig={jest.fn()} | |||||
onSelectedAlmInstanceChange={jest.fn()} | |||||
projects={undefined} | projects={undefined} | ||||
projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} | projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} | ||||
searching={false} | searching={false} |
importing={false} | importing={false} | ||||
loading={true} | loading={true} | ||||
loadingRepositories={Object {}} | loadingRepositories={Object {}} | ||||
onChangeConfig={[Function]} | |||||
onImportRepository={[Function]} | onImportRepository={[Function]} | ||||
onOpenProject={[Function]} | onOpenProject={[Function]} | ||||
onPersonalAccessTokenCreate={[Function]} | onPersonalAccessTokenCreate={[Function]} | ||||
onSearch={[Function]} | onSearch={[Function]} | ||||
onSelectRepository={[Function]} | onSelectRepository={[Function]} | ||||
onSelectedAlmInstanceChange={[Function]} | |||||
repositories={Object {}} | repositories={Object {}} | ||||
selectedAlmInstance={ | selectedAlmInstance={ | ||||
Object { | Object { |
className="display-flex-column" | className="display-flex-column" | ||||
> | > | ||||
<button | <button | ||||
className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right" | |||||
disabled={true} | |||||
className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right" | |||||
disabled={false} | |||||
onClick={[Function]} | onClick={[Function]} | ||||
type="button" | type="button" | ||||
> | > | ||||
> | > | ||||
onboarding.create_project.select_method.github | onboarding.create_project.select_method.github | ||||
</div> | </div> | ||||
<p | |||||
className="text-muted small spacer-top" | |||||
style={ | |||||
Object { | |||||
"lineHeight": 1.5, | |||||
} | |||||
} | |||||
> | |||||
onboarding.create_project.too_many_alm_instances_X.alm.github | |||||
</p> | |||||
</button> | </button> | ||||
</div> | </div> | ||||
<div | <div | ||||
className="display-flex-column" | className="display-flex-column" | ||||
> | > | ||||
<button | <button | ||||
className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right" | |||||
disabled={true} | |||||
className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right" | |||||
disabled={false} | |||||
onClick={[Function]} | onClick={[Function]} | ||||
type="button" | type="button" | ||||
> | > | ||||
> | > | ||||
onboarding.create_project.select_method.github | onboarding.create_project.select_method.github | ||||
</div> | </div> | ||||
<p | |||||
className="text-muted small spacer-top" | |||||
style={ | |||||
Object { | |||||
"lineHeight": 1.5, | |||||
} | |||||
} | |||||
> | |||||
onboarding.create_project.too_many_alm_instances_X.alm.github | |||||
</p> | |||||
</button> | </button> | ||||
</div> | </div> | ||||
<div | <div | ||||
className="display-flex-column" | className="display-flex-column" | ||||
> | > | ||||
<button | <button | ||||
className="button button-huge display-flex-column create-project-mode-type-alm disabled big-spacer-right" | |||||
disabled={true} | |||||
className="button button-huge display-flex-column create-project-mode-type-alm big-spacer-right" | |||||
disabled={false} | |||||
onClick={[Function]} | onClick={[Function]} | ||||
type="button" | type="button" | ||||
> | > | ||||
> | > | ||||
onboarding.create_project.select_method.github | onboarding.create_project.select_method.github | ||||
</div> | </div> | ||||
<p | |||||
className="text-muted small spacer-top" | |||||
style={ | |||||
Object { | |||||
"lineHeight": 1.5, | |||||
} | |||||
} | |||||
> | |||||
onboarding.create_project.too_many_alm_instances_X.alm.github | |||||
</p> | |||||
</button> | </button> | ||||
</div> | </div> | ||||
<div | <div |
id="create-project" | id="create-project" | ||||
> | > | ||||
<GitHubProjectCreate | <GitHubProjectCreate | ||||
almInstances={Array []} | |||||
canAdmin={false} | canAdmin={false} | ||||
loadingBindings={true} | loadingBindings={true} | ||||
location={ | location={ | ||||
"setRouteLeaveHook": [MockFunction], | "setRouteLeaveHook": [MockFunction], | ||||
} | } | ||||
} | } | ||||
settings={Array []} | |||||
/> | /> | ||||
</div> | </div> | ||||
</Fragment> | </Fragment> |
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
<DeferredSpinner | <DeferredSpinner | ||||
loading={false} | loading={false} | ||||
> | > | ||||
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<div | |||||
className="display-flex-justify-center" | |||||
> | |||||
<div | |||||
className="boxed-group padded width-50 huge-spacer-top" | |||||
> | |||||
<h2 | |||||
className="big-spacer-bottom" | |||||
> | |||||
onboarding.create_project.github.warning.title | |||||
</h2> | |||||
<Alert | |||||
variant="warning" | |||||
> | |||||
onboarding.create_project.github.warning.message | |||||
</Alert> | |||||
</div> | |||||
</div> | |||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
</div> | </div> | ||||
`; | `; | ||||
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<div | |||||
className="display-flex-justify-center" | |||||
> | |||||
<div | |||||
className="boxed-group padded width-50 huge-spacer-top" | |||||
> | |||||
<h2 | |||||
className="big-spacer-bottom" | |||||
> | |||||
onboarding.create_project.github.warning.title | |||||
</h2> | |||||
<Alert | |||||
variant="warning" | |||||
> | |||||
<FormattedMessage | |||||
defaultMessage="onboarding.create_project.github.warning.message_admin" | |||||
id="onboarding.create_project.github.warning.message_admin" | |||||
values={ | |||||
Object { | |||||
"link": <ForwardRef(Link) | |||||
to="/admin/settings?category=almintegration" | |||||
> | |||||
onboarding.create_project.github.warning.message_admin.link | |||||
</ForwardRef(Link)>, | |||||
} | |||||
} | |||||
/> | |||||
</Alert> | |||||
</div> | |||||
</div> | |||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
</div> | </div> | ||||
`; | `; | ||||
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
<DeferredSpinner | <DeferredSpinner | ||||
loading={false} | loading={false} | ||||
> | > | ||||
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
<DeferredSpinner | <DeferredSpinner | ||||
loading={false} | loading={false} | ||||
> | > | ||||
</span> | </span> | ||||
} | } | ||||
/> | /> | ||||
<AlmSettingsInstanceDropdown | |||||
almInstances={ | |||||
Array [ | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
Object { | |||||
"alm": "github", | |||||
"key": "key", | |||||
}, | |||||
] | |||||
} | |||||
onChangeConfig={[MockFunction]} | |||||
/> | |||||
<DeferredSpinner | <DeferredSpinner | ||||
loading={false} | loading={false} | ||||
> | > |
canAdmin={false} | canAdmin={false} | ||||
loading={false} | loading={false} | ||||
loadingMore={false} | loadingMore={false} | ||||
onChangeConfig={[Function]} | |||||
onImport={[Function]} | onImport={[Function]} | ||||
onLoadMore={[Function]} | onLoadMore={[Function]} | ||||
onPersonalAccessTokenCreated={[Function]} | onPersonalAccessTokenCreated={[Function]} | ||||
onSearch={[Function]} | onSearch={[Function]} | ||||
onSelectedAlmInstanceChange={[Function]} | |||||
projectsPaging={ | projectsPaging={ | ||||
Object { | Object { | ||||
"pageIndex": 1, | "pageIndex": 1, |
export const DEFAULT_BBS_PAGE_SIZE = 25; | export const DEFAULT_BBS_PAGE_SIZE = 25; | ||||
export const ALLOWED_MULTIPLE_CONFIGS = [AlmKeys.GitLab, AlmKeys.Azure]; | |||||
export const ALLOWED_MULTIPLE_CONFIGS = [AlmKeys.GitLab, AlmKeys.Azure, AlmKeys.GitHub]; |
wrapper = shallowRender(); | wrapper = shallowRender(); | ||||
await waitAndUpdate(wrapper); | await waitAndUpdate(wrapper); | ||||
expect(wrapper.state().boundAlms).toEqual([AlmKeys.Azure, AlmKeys.GitLab]); | |||||
expect(wrapper.state().boundAlms).toEqual([AlmKeys.Azure, AlmKeys.GitHub, AlmKeys.GitLab]); | |||||
}); | }); | ||||
function shallowRender(overrides: Partial<ProjectCreationMenu['props']> = {}) { | function shallowRender(overrides: Partial<ProjectCreationMenu['props']> = {}) { |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { components, OptionProps, SingleValueProps } from 'react-select'; | import { components, OptionProps, SingleValueProps } from 'react-select'; | ||||
import { translate } from '../../helpers/l10n'; | |||||
import { AlmSettingsInstance } from '../../types/alm-settings'; | import { AlmSettingsInstance } from '../../types/alm-settings'; | ||||
import Select from '../controls/Select'; | import Select from '../controls/Select'; | ||||
Option: optionRenderer, | Option: optionRenderer, | ||||
SingleValue: singleValueRenderer, | SingleValue: singleValueRenderer, | ||||
}} | }} | ||||
placeholder={translate('alm.configuration.selector.placeholder')} | |||||
getOptionValue={(opt) => opt.key} | getOptionValue={(opt) => opt.key} | ||||
value={instances.find((inst) => inst.key === initialValue)} | value={instances.find((inst) => inst.key === initialValue)} | ||||
/> | /> |
alm.github.short=GitHub | alm.github.short=GitHub | ||||
alm.gitlab=GitLab | alm.gitlab=GitLab | ||||
alm.gitlab.short=GitLab | alm.gitlab.short=GitLab | ||||
alm.configuration.selector.label=What DevOps platform do you want to import project from? | |||||
alm.configuration.selector.label=What DevOps platform configuration do you want to import projects from? | |||||
alm.configuration.selector.placeholder=Select a configuration | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
# | # | ||||
onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up? | onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up? | ||||
onboarding.create_project.bitbucketcloud.no_projects=No projects could be fetched from Bitbucket. Contact your system administrator, or {link}. | onboarding.create_project.bitbucketcloud.no_projects=No projects could be fetched from Bitbucket. Contact your system administrator, or {link}. | ||||
onboarding.create_project.bitbucketcloud.link=See on Bitbucket | onboarding.create_project.bitbucketcloud.link=See on Bitbucket | ||||
onboarding.create_project.github.title=Which GitHub repository do you want to set up? | |||||
onboarding.create_project.github.title=Github project onboarding | |||||
onboarding.create_project.github.choose_organization=Choose organization | onboarding.create_project.github.choose_organization=Choose organization | ||||
onboarding.create_project.github.warning.title=Could not connect to GitHub | onboarding.create_project.github.warning.title=Could not connect to GitHub | ||||
onboarding.create_project.github.warning.message=Please contact an administrator to configure GitHub integration. | onboarding.create_project.github.warning.message=Please contact an administrator to configure GitHub integration. |