@@ -22,6 +22,8 @@ import { mockGitlabProject } from '../../helpers/mocks/alm-integrations'; | |||
import { GitlabProject } from '../../types/alm-integration'; | |||
import { | |||
checkPersonalAccessTokenIsValid, | |||
getGithubClientId, | |||
getGithubOrganizations, | |||
getGitlabProjects, | |||
setAlmPersonalAccessToken, | |||
} from '../alm-integrations'; | |||
@@ -32,6 +34,8 @@ export default class AlmIntegrationsServiceMock { | |||
defaultAlmInstancePATMap: { [key: string]: boolean } = { | |||
'conf-final-1': false, | |||
'conf-final-2': true, | |||
'conf-github-1': false, | |||
'conf-github-2': true, | |||
}; | |||
defaultGitlabProjects: GitlabProject[] = [ | |||
@@ -45,6 +49,20 @@ export default class AlmIntegrationsServiceMock { | |||
mockGitlabProject({ name: 'Gitlab project 3', id: '3' }), | |||
]; | |||
defaultOrganizations = { | |||
paging: { | |||
pageIndex: 1, | |||
pageSize: 100, | |||
total: 1, | |||
}, | |||
organizations: [ | |||
{ | |||
key: 'org-1', | |||
name: 'org-1', | |||
}, | |||
], | |||
}; | |||
constructor() { | |||
this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | |||
this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); | |||
@@ -53,6 +71,8 @@ export default class AlmIntegrationsServiceMock { | |||
); | |||
(setAlmPersonalAccessToken as jest.Mock).mockImplementation(this.setAlmPersonalAccessToken); | |||
(getGitlabProjects as jest.Mock).mockImplementation(this.getGitlabProjects); | |||
(getGithubClientId as jest.Mock).mockImplementation(this.getGithubClientId); | |||
(getGithubOrganizations as jest.Mock).mockImplementation(this.getGithubOrganizations); | |||
} | |||
checkPersonalAccessTokenIsValid = (conf: string) => { | |||
@@ -79,6 +99,14 @@ export default class AlmIntegrationsServiceMock { | |||
this.gitlabProjects = gitlabProjects; | |||
} | |||
getGithubClientId = () => { | |||
return Promise.resolve({ clientId: 'clientId' }); | |||
}; | |||
getGithubOrganizations = () => { | |||
return Promise.resolve(this.defaultOrganizations); | |||
}; | |||
reset = () => { | |||
this.almInstancePATMap = cloneDeep(this.defaultAlmInstancePATMap); | |||
this.gitlabProjects = cloneDeep(this.defaultGitlabProjects); |
@@ -27,6 +27,8 @@ export default class AlmSettingsServiceMock { | |||
defaultSetting: AlmSettingsInstance[] = [ | |||
mockAlmSettingsInstance({ key: 'conf-final-1', 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() { |
@@ -273,7 +273,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State | |||
} | |||
}; | |||
onChangeConfig = (instance: AlmSettingsInstance) => { | |||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||
this.setState({ selectedAlmInstance: instance }, () => this.fetchData()); | |||
}; | |||
@@ -317,7 +317,7 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State | |||
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)} | |||
submittingToken={submittingToken} | |||
tokenValidationFailed={tokenValidationFailed} | |||
onChangeConfig={this.onChangeConfig} | |||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||
/> | |||
); | |||
} |
@@ -58,7 +58,7 @@ export interface AzureProjectCreateRendererProps { | |||
showPersonalAccessTokenForm?: boolean; | |||
submittingToken?: boolean; | |||
tokenValidationFailed: boolean; | |||
onChangeConfig: (instance: AlmSettingsInstance) => void; | |||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||
} | |||
export default function AzureProjectCreateRenderer(props: AzureProjectCreateRendererProps) { | |||
@@ -118,7 +118,7 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend | |||
<AlmSettingsInstanceDropdown | |||
almInstances={almInstances} | |||
selectedAlmInstance={selectedAlmInstance} | |||
onChangeConfig={props.onChangeConfig} | |||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||
/> | |||
{loading && <i className="spinner" />} |
@@ -42,7 +42,7 @@ import ManualProjectCreate from './ManualProjectCreate'; | |||
import './style.css'; | |||
import { CreateProjectModes } from './types'; | |||
interface Props extends WithAvailableFeaturesProps { | |||
export interface CreateProjectPageProps extends WithAvailableFeaturesProps { | |||
appState: AppState; | |||
location: Location; | |||
router: Router; | |||
@@ -66,7 +66,7 @@ const PROJECT_MODE_FOR_ALM_KEY = { | |||
[AlmKeys.GitLab]: CreateProjectModes.GitLab, | |||
}; | |||
export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
export class CreateProjectPage extends React.PureComponent<CreateProjectPageProps, State> { | |||
mounted = false; | |||
state: State = { | |||
azureSettings: [], | |||
@@ -207,7 +207,7 @@ export class CreateProjectPage extends React.PureComponent<Props, State> { | |||
location={location} | |||
onProjectCreate={this.handleProjectCreate} | |||
router={router} | |||
settings={githubSettings} | |||
almInstances={githubSettings} | |||
/> | |||
); | |||
} |
@@ -36,7 +36,7 @@ interface Props { | |||
canAdmin: boolean; | |||
loadingBindings: boolean; | |||
onProjectCreate: (projectKey: string) => void; | |||
settings: AlmSettingsInstance[]; | |||
almInstances: AlmSettingsInstance[]; | |||
location: Location; | |||
router: Router; | |||
} | |||
@@ -52,7 +52,7 @@ interface State { | |||
searchQuery: string; | |||
selectedOrganization?: GithubOrganization; | |||
selectedRepository?: GithubRepository; | |||
settings?: AlmSettingsInstance; | |||
selectedAlmInstance?: AlmSettingsInstance; | |||
} | |||
const REPOSITORY_PAGE_SIZE = 30; | |||
@@ -72,7 +72,7 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
repositories: [], | |||
repositoryPaging: { pageSize: REPOSITORY_PAGE_SIZE, total: 0, pageIndex: 1 }, | |||
searchQuery: '', | |||
settings: props.settings[0], | |||
selectedAlmInstance: this.getInitialSelectedAlmInstance(), | |||
}; | |||
this.triggerSearch = debounce(this.triggerSearch, 250); | |||
@@ -80,13 +80,14 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.initialize(); | |||
} | |||
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() | |||
); | |||
} | |||
} | |||
@@ -94,26 +95,39 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
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() { | |||
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 }); | |||
return; | |||
} else { | |||
this.setState({ error: false }); | |||
} | |||
this.setState({ error: false }); | |||
const code = location.query?.code; | |||
try { | |||
if (!code) { | |||
await this.redirectToGithub(settings); | |||
await this.redirectToGithub(selectedAlmInstance); | |||
} else { | |||
delete location.query.code; | |||
router.replace(location); | |||
await this.fetchOrganizations(settings, code); | |||
await this.fetchOrganizations(selectedAlmInstance, code); | |||
} | |||
} catch (e) { | |||
if (this.mounted) { | |||
@@ -122,33 +136,39 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
} | |||
} | |||
async redirectToGithub(settings: AlmSettingsInstance) { | |||
if (!settings.url) { | |||
async redirectToGithub(selectedAlmInstance: AlmSettingsInstance) { | |||
if (!selectedAlmInstance.url) { | |||
return; | |||
} | |||
const { clientId } = await getGithubClientId(settings.key); | |||
const { clientId } = await getGithubClientId(selectedAlmInstance.key); | |||
if (!clientId) { | |||
this.setState({ error: true }); | |||
return; | |||
} | |||
const queryParams = [ | |||
{ 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}`) | |||
.join('&'); | |||
let instanceRootUrl; | |||
// 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 | |||
instanceRootUrl = settings.url.replace('/api/v3', ''); | |||
instanceRootUrl = selectedAlmInstance.url.replace('/api/v3', ''); | |||
} else { | |||
// github.com | |||
instanceRootUrl = settings.url.replace('api.', ''); | |||
instanceRootUrl = selectedAlmInstance.url.replace('api.', ''); | |||
} | |||
// strip the trailing / | |||
@@ -156,8 +176,8 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
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) { | |||
this.setState({ loadingOrganizations: false, organizations }); | |||
@@ -166,9 +186,9 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
async fetchRepositories(params: { organizationKey: string; page?: number; query?: string }) { | |||
const { organizationKey, page = 1, query } = params; | |||
const { settings } = this.state; | |||
const { selectedAlmInstance } = this.state; | |||
if (!settings) { | |||
if (!selectedAlmInstance) { | |||
this.setState({ error: true }); | |||
return; | |||
} | |||
@@ -177,7 +197,7 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
try { | |||
const data = await getGithubRepositories({ | |||
almSetting: settings.key, | |||
almSetting: selectedAlmInstance.key, | |||
organization: organizationKey, | |||
pageSize: REPOSITORY_PAGE_SIZE, | |||
page, | |||
@@ -243,14 +263,14 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
}; | |||
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 }); | |||
try { | |||
const { project } = await importGithubRepository( | |||
settings.key, | |||
selectedAlmInstance.key, | |||
selectedOrganization.key, | |||
selectedRepository.key | |||
); | |||
@@ -264,8 +284,12 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
} | |||
}; | |||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||
this.setState({ selectedAlmInstance: instance }, () => this.initialize()); | |||
}; | |||
render() { | |||
const { canAdmin, loadingBindings } = this.props; | |||
const { canAdmin, loadingBindings, almInstances } = this.props; | |||
const { | |||
error, | |||
importing, | |||
@@ -277,6 +301,7 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
searchQuery, | |||
selectedOrganization, | |||
selectedRepository, | |||
selectedAlmInstance, | |||
} = this.state; | |||
return ( | |||
@@ -298,6 +323,9 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { | |||
repositories={repositories} | |||
selectedOrganization={selectedOrganization} | |||
selectedRepository={selectedRepository} | |||
almInstances={almInstances} | |||
selectedAlmInstance={selectedAlmInstance} | |||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||
/> | |||
); | |||
} |
@@ -37,8 +37,10 @@ import { translate } from '../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../helpers/system'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; | |||
import { AlmSettingsInstance } from '../../../types/alm-settings'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { Paging } from '../../../types/types'; | |||
import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; | |||
import CreateProjectPageHeader from './CreateProjectPageHeader'; | |||
export interface GitHubProjectCreateRendererProps { | |||
@@ -59,6 +61,9 @@ export interface GitHubProjectCreateRendererProps { | |||
searchQuery: string; | |||
selectedOrganization?: GithubOrganization; | |||
selectedRepository?: GithubRepository; | |||
almInstances: AlmSettingsInstance[]; | |||
selectedAlmInstance?: AlmSettingsInstance; | |||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||
} | |||
function orgToOption({ key, name }: GithubOrganization) { | |||
@@ -176,6 +181,8 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe | |||
organizations, | |||
selectedOrganization, | |||
selectedRepository, | |||
almInstances, | |||
selectedAlmInstance, | |||
} = props; | |||
if (loadingBindings) { | |||
@@ -212,7 +219,13 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe | |||
} | |||
/> | |||
{error ? ( | |||
<AlmSettingsInstanceDropdown | |||
almInstances={almInstances} | |||
selectedAlmInstance={selectedAlmInstance} | |||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||
/> | |||
{error && selectedAlmInstance && ( | |||
<div className="display-flex-justify-center"> | |||
<div className="boxed-group padded width-50 huge-spacer-top"> | |||
<h2 className="big-spacer-bottom"> | |||
@@ -239,7 +252,9 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe | |||
</Alert> | |||
</div> | |||
</div> | |||
) : ( | |||
)} | |||
{!error && ( | |||
<DeferredSpinner loading={loadingOrganizations}> | |||
<div className="form-field"> | |||
<label>{translate('onboarding.create_project.github.choose_organization')}</label> |
@@ -204,7 +204,7 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
await this.fetchInitialData(); | |||
}; | |||
onChangeConfig = (instance: AlmSettingsInstance) => { | |||
onSelectedAlmInstanceChange = (instance: AlmSettingsInstance) => { | |||
this.setState({ | |||
selectedAlmInstance: instance, | |||
showPersonalAccessTokenForm: true, | |||
@@ -248,7 +248,7 @@ export default class GitlabProjectCreate extends React.PureComponent<Props, Stat | |||
showPersonalAccessTokenForm={ | |||
showPersonalAccessTokenForm || Boolean(location.query.resetPat) | |||
} | |||
onChangeConfig={this.onChangeConfig} | |||
onSelectedAlmInstanceChange={this.onSelectedAlmInstanceChange} | |||
/> | |||
); | |||
} |
@@ -46,7 +46,7 @@ export interface GitlabProjectCreateRendererProps { | |||
almInstances?: AlmSettingsInstance[]; | |||
selectedAlmInstance?: AlmSettingsInstance; | |||
showPersonalAccessTokenForm?: boolean; | |||
onChangeConfig: (instance: AlmSettingsInstance) => void; | |||
onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void; | |||
} | |||
export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRendererProps) { | |||
@@ -84,7 +84,7 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe | |||
<AlmSettingsInstanceDropdown | |||
almInstances={almInstances} | |||
selectedAlmInstance={selectedAlmInstance} | |||
onChangeConfig={props.onChangeConfig} | |||
onChangeConfig={props.onSelectedAlmInstanceChange} | |||
/> | |||
{loading && <i className="spinner" />} |
@@ -83,7 +83,7 @@ function shallowRender(overrides: Partial<AzureProjectCreateRendererProps> = {}) | |||
]} | |||
showPersonalAccessTokenForm={false} | |||
submittingToken={false} | |||
onChangeConfig={jest.fn()} | |||
onSelectedAlmInstanceChange={jest.fn()} | |||
{...overrides} | |||
/> | |||
); |
@@ -25,16 +25,19 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; | |||
import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; | |||
import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; | |||
import { renderApp } from '../../../../helpers/testReactTestingUtils'; | |||
import CreateProjectPage from '../CreateProjectPage'; | |||
import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; | |||
jest.mock('../../../../api/alm-integrations'); | |||
jest.mock('../../../../api/alm-settings'); | |||
const original = window.location; | |||
let almIntegrationHandler: AlmIntegrationsServiceMock; | |||
let almSettingsHandler: AlmSettingsServiceMock; | |||
const ui = { | |||
gitlabCreateProjectButton: byText('onboarding.create_project.select_method.gitlab'), | |||
githubCreateProjectButton: byText('onboarding.create_project.select_method.github'), | |||
personalAccessTokenInput: byRole('textbox', { | |||
name: 'onboarding.create_project.enter_pat field_required', | |||
}), | |||
@@ -42,6 +45,10 @@ const ui = { | |||
}; | |||
beforeAll(() => { | |||
Object.defineProperty(window, 'location', { | |||
configurable: true, | |||
value: { replace: jest.fn() }, | |||
}); | |||
almIntegrationHandler = new AlmIntegrationsServiceMock(); | |||
almSettingsHandler = new AlmSettingsServiceMock(); | |||
}); | |||
@@ -51,6 +58,10 @@ afterEach(() => { | |||
almSettingsHandler.reset(); | |||
}); | |||
afterAll(() => { | |||
Object.defineProperty(window, 'location', { configurable: true, value: original }); | |||
}); | |||
describe('Gitlab onboarding page', () => { | |||
it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { | |||
const user = userEvent.setup(); | |||
@@ -100,6 +111,28 @@ describe('Gitlab onboarding page', () => { | |||
}); | |||
}); | |||
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} />); | |||
} |
@@ -63,7 +63,7 @@ beforeEach(() => { | |||
}); | |||
it('should handle no settings', async () => { | |||
const wrapper = shallowRender({ settings: [] }); | |||
const wrapper = shallowRender({ almInstances: [] }); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().error).toBe(true); | |||
}); | |||
@@ -78,13 +78,13 @@ it('should redirect when no code', async () => { | |||
it('should redirect when no code - github.com', async () => { | |||
const wrapper = shallowRender({ | |||
settings: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], | |||
almInstances: [mockAlmSettingsInstance({ key: 'a', url: 'api.github.com' })], | |||
}); | |||
await waitAndUpdate(wrapper); | |||
expect(getGithubClientId).toHaveBeenCalled(); | |||
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' | |||
); | |||
}); | |||
@@ -239,7 +239,7 @@ function shallowRender(props: Partial<GitHubProjectCreate['props']> = {}) { | |||
location={mockLocation()} | |||
onProjectCreate={jest.fn()} | |||
router={mockRouter()} | |||
settings={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} | |||
almInstances={[mockAlmSettingsInstance({ key: 'a', url: 'geh.company.com/api/v3' })]} | |||
{...props} | |||
/> | |||
); |
@@ -23,6 +23,7 @@ import Radio from '../../../../components/controls/Radio'; | |||
import SearchBox from '../../../../components/controls/SearchBox'; | |||
import Select from '../../../../components/controls/Select'; | |||
import { mockGitHubRepository } from '../../../../helpers/mocks/alm-integrations'; | |||
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; | |||
import { GithubOrganization } from '../../../../types/alm-integration'; | |||
import GitHubProjectCreateRenderer, { | |||
GitHubProjectCreateRendererProps, | |||
@@ -116,6 +117,8 @@ function shallowRender(props: Partial<GitHubProjectCreateRendererProps> = {}) { | |||
onSearch={jest.fn()} | |||
onSelectOrganization={jest.fn()} | |||
onSelectRepository={jest.fn()} | |||
onSelectedAlmInstanceChange={jest.fn()} | |||
almInstances={[mockAlmSettingsInstance(), mockAlmSettingsInstance()]} | |||
organizations={[]} | |||
repositoryPaging={{ total: 0, pageIndex: 1, pageSize: 30 }} | |||
searchQuery="" |
@@ -47,7 +47,7 @@ function shallowRender(props: Partial<GitlabProjectCreateRendererProps> = {}) { | |||
onLoadMore={jest.fn()} | |||
onPersonalAccessTokenCreated={jest.fn()} | |||
onSearch={jest.fn()} | |||
onChangeConfig={jest.fn()} | |||
onSelectedAlmInstanceChange={jest.fn()} | |||
projects={undefined} | |||
projectsPaging={{ pageIndex: 1, pageSize: 30, total: 0 }} | |||
searching={false} |
@@ -14,12 +14,12 @@ exports[`should render correctly 1`] = ` | |||
importing={false} | |||
loading={true} | |||
loadingRepositories={Object {}} | |||
onChangeConfig={[Function]} | |||
onImportRepository={[Function]} | |||
onOpenProject={[Function]} | |||
onPersonalAccessTokenCreate={[Function]} | |||
onSearch={[Function]} | |||
onSelectRepository={[Function]} | |||
onSelectedAlmInstanceChange={[Function]} | |||
repositories={Object {}} | |||
selectedAlmInstance={ | |||
Object { |
@@ -189,8 +189,8 @@ exports[`should render correctly: invalid configs, admin 1`] = ` | |||
className="display-flex-column" | |||
> | |||
<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]} | |||
type="button" | |||
> | |||
@@ -204,16 +204,6 @@ exports[`should render correctly: invalid configs, admin 1`] = ` | |||
> | |||
onboarding.create_project.select_method.github | |||
</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> | |||
</div> | |||
<div | |||
@@ -329,8 +319,8 @@ exports[`should render correctly: invalid configs, admin 2`] = ` | |||
className="display-flex-column" | |||
> | |||
<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]} | |||
type="button" | |||
> | |||
@@ -344,16 +334,6 @@ exports[`should render correctly: invalid configs, admin 2`] = ` | |||
> | |||
onboarding.create_project.select_method.github | |||
</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> | |||
</div> | |||
<div | |||
@@ -469,8 +449,8 @@ exports[`should render correctly: invalid configs, not admin 1`] = ` | |||
className="display-flex-column" | |||
> | |||
<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]} | |||
type="button" | |||
> | |||
@@ -484,16 +464,6 @@ exports[`should render correctly: invalid configs, not admin 1`] = ` | |||
> | |||
onboarding.create_project.select_method.github | |||
</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> | |||
</div> | |||
<div |
@@ -245,6 +245,7 @@ exports[`should render correctly for github mode 1`] = ` | |||
id="create-project" | |||
> | |||
<GitHubProjectCreate | |||
almInstances={Array []} | |||
canAdmin={false} | |||
loadingBindings={true} | |||
location={ | |||
@@ -273,7 +274,6 @@ exports[`should render correctly for github mode 1`] = ` | |||
"setRouteLeaveHook": [MockFunction], | |||
} | |||
} | |||
settings={Array []} | |||
/> | |||
</div> | |||
</Fragment> |
@@ -17,6 +17,21 @@ exports[`should render correctly: default 1`] = ` | |||
</span> | |||
} | |||
/> | |||
<AlmSettingsInstanceDropdown | |||
almInstances={ | |||
Array [ | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
] | |||
} | |||
onChangeConfig={[MockFunction]} | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
@@ -54,24 +69,21 @@ exports[`should render correctly: error 1`] = ` | |||
</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> | |||
`; | |||
@@ -92,36 +104,21 @@ exports[`should render correctly: error for admin 1`] = ` | |||
</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> | |||
`; | |||
@@ -161,6 +158,21 @@ exports[`should render correctly: no repositories 1`] = ` | |||
</span> | |||
} | |||
/> | |||
<AlmSettingsInstanceDropdown | |||
almInstances={ | |||
Array [ | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
] | |||
} | |||
onChangeConfig={[MockFunction]} | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
@@ -214,6 +226,21 @@ exports[`should render correctly: organizations 1`] = ` | |||
</span> | |||
} | |||
/> | |||
<AlmSettingsInstanceDropdown | |||
almInstances={ | |||
Array [ | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
] | |||
} | |||
onChangeConfig={[MockFunction]} | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
@@ -279,6 +306,21 @@ exports[`should render correctly: repositories 1`] = ` | |||
</span> | |||
} | |||
/> | |||
<AlmSettingsInstanceDropdown | |||
almInstances={ | |||
Array [ | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
Object { | |||
"alm": "github", | |||
"key": "key", | |||
}, | |||
] | |||
} | |||
onChangeConfig={[MockFunction]} | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> |
@@ -13,11 +13,11 @@ exports[`should render correctly 1`] = ` | |||
canAdmin={false} | |||
loading={false} | |||
loadingMore={false} | |||
onChangeConfig={[Function]} | |||
onImport={[Function]} | |||
onLoadMore={[Function]} | |||
onPersonalAccessTokenCreated={[Function]} | |||
onSearch={[Function]} | |||
onSelectedAlmInstanceChange={[Function]} | |||
projectsPaging={ | |||
Object { | |||
"pageIndex": 1, |
@@ -23,4 +23,4 @@ export const PROJECT_NAME_MAX_LEN = 255; | |||
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]; |
@@ -121,7 +121,7 @@ it('should filter alm bindings appropriately', async () => { | |||
wrapper = shallowRender(); | |||
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']> = {}) { |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { components, OptionProps, SingleValueProps } from 'react-select'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { AlmSettingsInstance } from '../../types/alm-settings'; | |||
import Select from '../controls/Select'; | |||
@@ -68,6 +69,7 @@ export default function AlmSettingsInstanceSelector(props: Props) { | |||
Option: optionRenderer, | |||
SingleValue: singleValueRenderer, | |||
}} | |||
placeholder={translate('alm.configuration.selector.placeholder')} | |||
getOptionValue={(opt) => opt.key} | |||
value={instances.find((inst) => inst.key === initialValue)} | |||
/> |
@@ -389,7 +389,8 @@ alm.github=GitHub | |||
alm.github.short=GitHub | |||
alm.gitlab=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 | |||
#------------------------------------------------------------------------------ | |||
# | |||
@@ -3591,7 +3592,7 @@ onboarding.create_project.azure.no_results=No repositories match your search que | |||
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.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.warning.title=Could not connect to GitHub | |||
onboarding.create_project.github.warning.message=Please contact an administrator to configure GitHub integration. |