interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean;
loadingBindings: boolean;
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
}
if (this.mounted) {
this.setState({ importing: false });
if (createdProject) {
- this.props.onProjectCreate([createdProject.key]);
+ this.props.onProjectCreate(createdProject.key);
}
}
};
canAdmin: boolean;
settings: AlmSettingsInstance[];
loadingBindings: boolean;
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
}
interface State {
this.setState({ importingSlug: undefined });
if (result) {
- this.props.onProjectCreate([result.project.key]);
+ this.props.onProjectCreate(result.project.key);
}
}
};
canAdmin: boolean;
bitbucketSettings: AlmSettingsInstance[];
loadingBindings: boolean;
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
}
interface State {
.then(({ project: { key } }) => {
if (this.mounted) {
this.setState({ importing: false });
- this.props.onProjectCreate([key]);
+ this.props.onProjectCreate(key);
}
})
.catch(() => {
loading={loading || loadingBindings}
onImportRepository={this.handleImportRepository}
onPersonalAccessTokenCreated={this.handlePersonalAccessTokenCreated}
- onProjectCreate={this.props.onProjectCreate}
onSearch={this.handleSearch}
onSelectRepository={this.handleSelectRepository}
projectRepositories={projectRepositories}
onSearch: (query: string) => void;
onSelectRepository: (repo: BitbucketRepository) => void;
onPersonalAccessTokenCreated: () => void;
- onProjectCreate: (projectKeys: string[]) => void;
projects?: BitbucketProject[];
projectRepositories?: BitbucketProjectRepositories;
resetPat: boolean;
appState: Pick<T.AppState, 'canAdmin'>;
loadingBindings: boolean;
onSelectMode: (mode: CreateProjectModes) => void;
+ onConfigMode: (mode: AlmKeys) => void;
}
const DEFAULT_ICON_SIZE = 80;
loadingBindings
} = props;
- const hasBitbucketCloud = almCounts[AlmKeys.BitbucketCloud] > 0;
- const isBitbucket = alm === AlmKeys.BitbucketServer;
+ const hasBitbucketCloudConf = almCounts[AlmKeys.BitbucketCloud] > 0;
+ const isBitbucketOption = alm === AlmKeys.BitbucketServer;
- const count = isBitbucket
+ const count = isBitbucketOption
? almCounts[AlmKeys.BitbucketServer] + almCounts[AlmKeys.BitbucketCloud]
: almCounts[alm];
const hasConfig = count > 0;
const hasTooManyConfig = count > 1;
const disabled = loadingBindings || hasTooManyConfig || (!hasConfig && !canAdmin);
+ const onClick = () => {
+ if (hasTooManyConfig || (!hasConfig && !canAdmin)) {
+ return null;
+ }
+
+ if (!hasConfig && canAdmin) {
+ return props.onConfigMode(alm);
+ }
+
+ return props.onSelectMode(
+ isBitbucketOption && hasBitbucketCloudConf ? CreateProjectModes.BitbucketCloud : mode
+ );
+ };
+
return (
<div className="display-flex-column">
<button
{ disabled, 'big-spacer-right': !last }
)}
disabled={disabled}
- onClick={() =>
- props.onSelectMode(
- isBitbucket && hasBitbucketCloud ? CreateProjectModes.BitbucketCloud : mode
- )
- }
+ onClick={onClick}
type="button">
<img
alt="" // Should be ignored by screen readers
import { withAppState } from '../../../components/hoc/withAppState';
import { getProjectUrl } from '../../../helpers/urls';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
+import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm';
import AzureProjectCreate from './AzureProjectCreate';
import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate';
import BitbucketProjectCreate from './BitbucketProjectCreate';
githubSettings: AlmSettingsInstance[];
gitlabSettings: AlmSettingsInstance[];
loading: boolean;
+ creatingAlmDefinition?: AlmKeys;
}
+const PROJECT_MODE_FOR_ALM_KEY = {
+ [AlmKeys.Azure]: CreateProjectModes.AzureDevOps,
+ [AlmKeys.BitbucketCloud]: CreateProjectModes.BitbucketCloud,
+ [AlmKeys.BitbucketServer]: CreateProjectModes.BitbucketServer,
+ [AlmKeys.GitHub]: CreateProjectModes.GitHub,
+ [AlmKeys.GitLab]: CreateProjectModes.GitLab
+};
+
export class CreateProjectPage extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
fetchAlmBindings = () => {
this.setState({ loading: true });
- getAlmSettings()
+ return getAlmSettings()
.then(almSettings => {
if (this.mounted) {
this.setState({
});
};
- handleProjectCreate = (projectKeys: string[]) => {
- if (projectKeys.length === 1) {
- this.props.router.push(getProjectUrl(projectKeys[0]));
+ handleModeConfig = (alm: AlmKeys) => {
+ this.setState({ creatingAlmDefinition: alm });
+ };
+
+ handleProjectCreate = (projectKey: string) => {
+ this.props.router.push(getProjectUrl(projectKey));
+ };
+
+ handleOnCancelCreation = () => {
+ this.setState({ creatingAlmDefinition: undefined });
+ };
+
+ handleAfterSubmit = async () => {
+ let { creatingAlmDefinition: createdAlmDefinition } = this.state;
+
+ this.setState({ creatingAlmDefinition: undefined });
+
+ await this.fetchAlmBindings();
+
+ if (this.mounted && createdAlmDefinition) {
+ const { bitbucketCloudSettings } = this.state;
+
+ if (createdAlmDefinition === AlmKeys.BitbucketServer && bitbucketCloudSettings.length > 0) {
+ createdAlmDefinition = AlmKeys.BitbucketCloud;
+ }
+
+ this.handleModeSelect(PROJECT_MODE_FOR_ALM_KEY[createdAlmDefinition]);
}
};
- renderForm(mode?: CreateProjectModes) {
+ renderProjectCreation(mode?: CreateProjectModes) {
const {
appState: { canAdmin },
location,
almCounts={almCounts}
loadingBindings={loading}
onSelectMode={this.handleModeSelect}
+ onConfigMode={this.handleModeConfig}
/>
);
}
render() {
const { location } = this.props;
+ const { creatingAlmDefinition } = this.state;
const mode: CreateProjectModes | undefined = location.query?.mode;
return (
<Helmet title={translate('onboarding.create_project.select_method')} titleTemplate="%s" />
<A11ySkipTarget anchor="create_project_main" />
<div className="page page-limited huge-spacer-bottom position-relative" id="create-project">
- {this.renderForm(mode)}
+ {this.renderProjectCreation(mode)}
+ {creatingAlmDefinition && (
+ <AlmBindingDefinitionForm
+ alm={creatingAlmDefinition}
+ alreadyHaveInstanceConfigured={false}
+ onCancel={this.handleOnCancelCreation}
+ afterSubmit={this.handleAfterSubmit}
+ />
+ )}
</div>
</>
);
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean;
loadingBindings: boolean;
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
}
selectedRepository.key
);
- this.props.onProjectCreate([project.key]);
+ this.props.onProjectCreate(project.key);
} finally {
if (this.mounted) {
this.setState({ importing: false });
interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean;
loadingBindings: boolean;
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
settings: AlmSettingsInstance[];
}
this.setState({ importingGitlabProjectId: undefined });
if (result) {
- this.props.onProjectCreate([result.project.key]);
+ this.props.onProjectCreate(result.project.key);
}
}
};
import './ManualProjectCreate.css';
interface Props {
- onProjectCreate: (projectKeys: string[]) => void;
+ onProjectCreate: (projectKey: string) => void;
}
interface State {
project: state.projectKey,
name: (state.projectName || state.projectKey).trim()
}).then(
- ({ project }) => this.props.onProjectCreate([project.key]),
+ ({ project }) => this.props.onProjectCreate(project.key),
() => {
if (this.mounted) {
this.setState({ submitting: false });
expect(importAzureRepository).toBeCalledWith('foo', repository.projectName, repository.name);
await waitAndUpdate(wrapper);
- expect(onProjectCreate).toBeCalledWith(['baz']);
+ expect(onProjectCreate).toBeCalledWith('baz');
expect(wrapper.state().importing).toBe(false);
});
});
await wrapper.instance().handleImport('slug-test');
expect(importBitbucketCloudRepository).toHaveBeenCalledWith('key', 'slug-test');
- expect(onProjectCreate).toHaveBeenCalledWith(['project-key']);
+ expect(onProjectCreate).toHaveBeenCalledWith('project-key');
});
it('Should behave correctly when import fail', async () => {
instance.handleImportRepository();
expect(importBitbucketServerProject).toBeCalledWith('foo', repo.projectKey, repo.slug);
await waitAndUpdate(wrapper);
- expect(onProjectCreate).toBeCalledWith(['baz']);
+ expect(onProjectCreate).toBeCalledWith('baz');
});
it('should correctly handle search', async () => {
loading={false}
onImportRepository={jest.fn()}
onPersonalAccessTokenCreated={jest.fn()}
- onProjectCreate={jest.fn()}
onSearch={jest.fn()}
onSelectRepository={jest.fn()}
projectRepositories={{ foo: { allShown: true, repositories: [mockBitbucketRepository()] } }}
)
).toMatchSnapshot('invalid configs, admin');
expect(
- shallowRender({ appState: { canAdmin: true } }, { [AlmKeys.BitbucketServer]: 0 })
+ shallowRender(
+ { appState: { canAdmin: true } },
+ {
+ [AlmKeys.Azure]: 0,
+ [AlmKeys.BitbucketCloud]: 0,
+ [AlmKeys.BitbucketServer]: 0,
+ [AlmKeys.GitHub]: 0,
+ [AlmKeys.GitLab]: 0
+ }
+ )
).toMatchSnapshot('no alm conf yet, admin');
});
expect(onSelectMode).toBeCalledWith(CreateProjectModes.GitLab);
onSelectMode.mockClear();
- wrapper = shallowRender({ onSelectMode }, { [AlmKeys.BitbucketCloud]: 1 });
+ wrapper = shallowRender(
+ { onSelectMode },
+ { [AlmKeys.BitbucketCloud]: 1, [AlmKeys.BitbucketServer]: 0 }
+ );
click(wrapper.find(almButton).at(1));
expect(onSelectMode).toBeCalledWith(CreateProjectModes.BitbucketCloud);
onSelectMode.mockClear();
});
+it('should call the proper click handler', () => {
+ const almButton = 'button.create-project-mode-type-alm';
+
+ const onSelectMode = jest.fn();
+ const onConfigMode = jest.fn();
+
+ let wrapper = shallowRender({ onSelectMode, onConfigMode }, { [AlmKeys.Azure]: 2 });
+
+ click(wrapper.find(almButton).at(0));
+ expect(onConfigMode).not.toHaveBeenCalled();
+ expect(onSelectMode).not.toHaveBeenCalled();
+ onConfigMode.mockClear();
+ onSelectMode.mockClear();
+
+ wrapper = shallowRender({ onSelectMode, onConfigMode });
+
+ click(wrapper.find(almButton).at(0));
+ expect(onConfigMode).not.toHaveBeenCalled();
+ expect(onSelectMode).toHaveBeenCalledWith(CreateProjectModes.AzureDevOps);
+ onConfigMode.mockClear();
+ onSelectMode.mockClear();
+
+ wrapper = shallowRender(
+ { onSelectMode, onConfigMode, appState: { canAdmin: true } },
+ { [AlmKeys.Azure]: 0 }
+ );
+
+ click(wrapper.find(almButton).at(0));
+ expect(onConfigMode).toHaveBeenCalledWith(CreateProjectModes.AzureDevOps);
+ expect(onSelectMode).not.toHaveBeenCalled();
+ onConfigMode.mockClear();
+ onSelectMode.mockClear();
+});
+
function shallowRender(
props: Partial<CreateProjectModeSelectionProps> = {},
almCountOverrides = {}
) {
const almCounts = {
- [AlmKeys.Azure]: 0,
+ [AlmKeys.Azure]: 1,
[AlmKeys.BitbucketCloud]: 0,
[AlmKeys.BitbucketServer]: 1,
- [AlmKeys.GitHub]: 0,
- [AlmKeys.GitLab]: 0,
+ [AlmKeys.GitHub]: 1,
+ [AlmKeys.GitLab]: 1,
...almCountOverrides
};
return shallow<CreateProjectModeSelectionProps>(
appState={{ canAdmin: false }}
loadingBindings={false}
onSelectMode={jest.fn()}
+ onConfigMode={jest.fn()}
{...props}
/>
);
expect(wrapper).toMatchSnapshot();
});
+it('should submit alm configuration creation properly for BBC', async () => {
+ const push = jest.fn();
+ const wrapper = shallowRender({ router: mockRouter({ push }) });
+
+ wrapper
+ .find(CreateProjectModeSelection)
+ .props()
+ .onConfigMode(AlmKeys.BitbucketServer);
+ expect(wrapper.state().creatingAlmDefinition).toBe(AlmKeys.BitbucketServer);
+
+ (getAlmSettings as jest.Mock).mockResolvedValueOnce([{ alm: AlmKeys.BitbucketCloud }]);
+ wrapper
+ .find(AlmBindingDefinitionForm)
+ .props()
+ .afterSubmit({ key: 'test-key' });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state().creatingAlmDefinition).toBeUndefined();
+ expect(getAlmSettings).toHaveBeenCalled();
+ expect(push).toHaveBeenCalledWith({ pathname: '/path', query: { mode: AlmKeys.BitbucketCloud } });
+});
+
function shallowRender(props: Partial<CreateProjectPage['props']> = {}) {
return shallow<CreateProjectPage>(
<CreateProjectPage
selectedOrganization.key,
selectedRepository.key
);
- expect(onProjectCreate).toBeCalledWith([project.key]);
+ expect(onProjectCreate).toBeCalledWith(project.key);
});
function shallowRender(props: Partial<GitHubProjectCreate['props']> = {}) {
await waitAndUpdate(wrapper);
expect(wrapper.state().importingGitlabProjectId).toBeUndefined();
- expect(onProjectCreate).toBeCalledWith([createdProjectkey]);
+ expect(onProjectCreate).toBeCalledWith(createdProjectkey);
});
it('should do nothing with missing settings', async () => {
});
await waitAndUpdate(wrapper);
- expect(onProjectCreate).toBeCalledWith(['bar']);
+ expect(onProjectCreate).toBeCalledWith('bar');
});
it('should not display any status when the name is not defined', () => {
loading={false}
onImportRepository={[Function]}
onPersonalAccessTokenCreated={[Function]}
- onProjectCreate={[MockFunction]}
onSearch={[Function]}
onSelectRepository={[Function]}
resetPat={false}
loading={false}
onImportRepository={[Function]}
onPersonalAccessTokenCreated={[Function]}
- onProjectCreate={[MockFunction]}
onSearch={[Function]}
onSelectRepository={[Function]}
projects={
loading={false}
onImportRepository={[Function]}
onPersonalAccessTokenCreated={[Function]}
- onProjectCreate={[MockFunction]}
onSearch={[Function]}
onSelectRepository={[Function]}
resetPat={false}
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"
>
>
onboarding.create_project.select_method.azure
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.alm_not_configured
- </p>
</button>
</div>
<div
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"
>
>
onboarding.create_project.select_method.github
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.alm_not_configured
- </p>
</button>
</div>
<div
className="display-flex-column"
>
<button
- className="button button-huge display-flex-column create-project-mode-type-alm disabled"
- disabled={true}
+ className="button button-huge display-flex-column create-project-mode-type-alm"
+ disabled={false}
onClick={[Function]}
type="button"
>
>
onboarding.create_project.select_method.gitlab
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.alm_not_configured
- </p>
</button>
</div>
</div>
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"
>
>
onboarding.create_project.select_method.azure
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.alm_not_configured
- </p>
</button>
</div>
<div
className="display-flex-column"
>
<button
- className="button button-huge display-flex-column create-project-mode-type-alm disabled"
- disabled={true}
+ className="button button-huge display-flex-column create-project-mode-type-alm"
+ disabled={false}
onClick={[Function]}
type="button"
>
>
onboarding.create_project.select_method.gitlab
</div>
- <p
- className="text-muted small spacer-top"
- style={
- Object {
- "lineHeight": 1.5,
- }
- }
- >
- onboarding.create_project.alm_not_configured
- </p>
</button>
</div>
</div>
}
}
loadingBindings={true}
+ onConfigMode={[Function]}
onSelectMode={[Function]}
/>
</div>
} from '../../../../types/alm-settings';
import AlmBindingDefinitionFormRenderer from './AlmBindingDefinitionFormRenderer';
-export interface AlmBindingDefinitionFormChildrenProps {
- formData: AlmBindingDefinition;
- onFieldChange: (fieldId: string, value: string) => void;
-}
-
interface Props {
alm: AlmKeys;
bindingDefinition?: AlmBindingDefinition;
alreadyHaveInstanceConfigured: boolean;
- onDelete?: (definitionKey: string) => void;
- onEdit?: (definitionKey: string) => void;
onCancel?: () => void;
afterSubmit?: (data: AlmBindingDefinitionBase) => void;
}