return post('/api/alm_settings/set_azure_binding', data).catch(throwGlobalError);
}
+export function setProjectBitbucketBinding(data: T.BitbucketProjectAlmBinding) {
+ return post('/api/alm_settings/set_bitbucket_binding', data).catch(throwGlobalError);
+}
+
export function setProjectGithubBinding(data: T.GithubProjectAlmBinding) {
return post('/api/alm_settings/set_github_binding', data).catch(throwGlobalError);
}
"key": "github",
"label": "Github Enterprise",
},
+ Object {
+ "key": "bitbucket",
+ "label": "Bitbucket Server",
+ },
Object {
"key": "azure",
"label": "Azure DevOps Server",
"key": "github",
"label": "Github Enterprise",
},
+ Object {
+ "key": "bitbucket",
+ "label": "Bitbucket Server",
+ },
Object {
"key": "azure",
"label": "Azure DevOps Server",
"key": "github",
"label": "Github Enterprise",
},
+ Object {
+ "key": "bitbucket",
+ "label": "Bitbucket Server",
+ },
Object {
"key": "azure",
"label": "Azure DevOps Server",
"key": "github",
"label": "Github Enterprise",
},
+ Object {
+ "key": "bitbucket",
+ "label": "Bitbucket Server",
+ },
Object {
"key": "azure",
"label": "Azure DevOps Server",
getAlmSettings,
getProjectAlmBinding,
setProjectAzureBinding,
+ setProjectBitbucketBinding,
setProjectGithubBinding
} from '../../../../api/almSettings';
import throwGlobalError from '../../../../app/utils/throwGlobalError';
success: boolean;
}
-const FIELDS_BY_ALM: { [almKey: string]: Array<'repository'> } = {
+const FIELDS_BY_ALM: {
+ [almKey: string]: Array<'repository' | 'repositoryKey' | 'repositorySlug'>;
+} = {
[ALM_KEYS.AZURE]: [],
+ [ALM_KEYS.BITBUCKET]: ['repositoryKey', 'repositorySlug'],
[ALM_KEYS.GITHUB]: ['repository']
};
submitProjectAlmBinding(
alm: ALM_KEYS,
key: string,
- almSpecificFields?: { repository?: string }
+ almSpecificFields?: { repository?: string; repositoryKey?: string; repositorySlug?: string }
): Promise<void> {
const almSetting = key;
const project = this.props.component.key;
almSetting,
project
});
+ case ALM_KEYS.BITBUCKET: {
+ if (!almSpecificFields) {
+ return Promise.reject();
+ }
+ const { repositoryKey = '', repositorySlug = '' } = almSpecificFields;
+ return setProjectBitbucketBinding({
+ almSetting,
+ project,
+ repositoryKey,
+ repositorySlug
+ });
+ }
case ALM_KEYS.GITHUB: {
const repository = almSpecificFields && almSpecificFields.repository;
if (!repository) {
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { Button, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
+import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import Select from 'sonar-ui-common/components/controls/Select';
import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
return v.url ? `${v.key} — ${v.url}` : v.key;
}
+function renderField(props: {
+ help?: boolean;
+ helpParams?: { [key: string]: string | number | boolean | Date | JSX.Element | null | undefined };
+ id: string;
+ onFieldChange: (id: keyof T.ProjectAlmBinding, value: string) => void;
+ propKey: keyof T.ProjectAlmBinding;
+ value: string;
+}) {
+ const { help, helpParams, id, propKey, value, onFieldChange } = props;
+ return (
+ <div className="form-field">
+ <label className="display-flex-center" htmlFor={id}>
+ {translate('settings.pr_decoration.binding.form', id)}
+ <em className="mandatory spacer-right">*</em>
+ {help && (
+ <HelpTooltip
+ overlay={
+ <FormattedMessage
+ defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')}
+ id={`settings.pr_decoration.binding.form.${id}.help`}
+ values={helpParams}
+ />
+ }
+ placement="right"
+ />
+ )}
+ </label>
+ <input
+ className="input-super-large"
+ id={id}
+ maxLength={256}
+ name={id}
+ onChange={e => onFieldChange(propKey, e.currentTarget.value)}
+ type="text"
+ value={value}
+ />
+ </div>
+ );
+}
+
export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
const {
- formData: { repository, key },
+ formData: { key, repository, repositoryKey, repositorySlug },
hasBinding,
instances,
isValid,
/>
</div>
- {alm === ALM_KEYS.GITHUB && (
- <div className="form-field">
- <label htmlFor="repository">
- {translate('settings.pr_decoration.binding.form.repository')}
- <em className="mandatory spacer-right">*</em>
- </label>
- <input
- className="input-super-large"
- id="repository"
- maxLength={256}
- name="repository"
- onChange={e => props.onFieldChange('repository', e.currentTarget.value)}
- type="text"
- value={repository}
- />
- </div>
+ {alm === ALM_KEYS.BITBUCKET && (
+ <>
+ {renderField({
+ help: true,
+ helpParams: {
+ example: (
+ <>
+ {'.../projects/'}
+ <strong>{'{KEY}'}</strong>
+ {'/repos/{SLUG}/browse'}
+ </>
+ )
+ },
+ id: 'repository_key',
+ onFieldChange: props.onFieldChange,
+ propKey: 'repositoryKey',
+ value: repositoryKey || ''
+ })}
+ {renderField({
+ help: true,
+ helpParams: {
+ example: (
+ <>
+ {'.../projects/{KEY}/repos/'}
+ <strong>{'{SLUG}'}</strong>
+ {'/browse'}
+ </>
+ )
+ },
+ id: 'repository_slug',
+ onFieldChange: props.onFieldChange,
+ propKey: 'repositorySlug',
+ value: repositorySlug || ''
+ })}
+ </>
)}
+ {alm === ALM_KEYS.GITHUB &&
+ renderField({
+ help: true,
+ helpParams: { example: 'SonarSource/sonarqube' },
+ id: 'repository',
+ onFieldChange: props.onFieldChange,
+ propKey: 'repository',
+ value: repository || ''
+ })}
+
<div className="display-flex-center">
<DeferredSpinner className="spacer-right" loading={saving} />
<SubmitButton className="spacer-right" disabled={saving || !isValid}>
getAlmSettings,
getProjectAlmBinding,
setProjectAzureBinding,
+ setProjectBitbucketBinding,
setProjectGithubBinding
} from '../../../../../api/almSettings';
import { mockComponent } from '../../../../../helpers/testMocks';
getAlmSettings: jest.fn().mockResolvedValue([]),
getProjectAlmBinding: jest.fn().mockResolvedValue(undefined),
setProjectAzureBinding: jest.fn().mockResolvedValue(undefined),
+ setProjectBitbucketBinding: jest.fn().mockResolvedValue(undefined),
setProjectGithubBinding: jest.fn().mockResolvedValue(undefined),
deleteProjectAlmBinding: jest.fn().mockResolvedValue(undefined)
}));
expect(wrapper.state().hasBinding).toBe(false);
});
-it('should handle submit to github or azure', async () => {
+it('should handle submit to github, azure or bitbucket', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
const instances = [
{ key: 'github', alm: ALM_KEYS.GITHUB },
- { key: 'azure', alm: ALM_KEYS.AZURE }
+ { key: 'azure', alm: ALM_KEYS.AZURE },
+ { key: 'bitbucket', alm: ALM_KEYS.BITBUCKET }
];
// Github
});
expect(wrapper.state().hasBinding).toBe(true);
expect(wrapper.state().success).toBe(true);
+
+ // bitbucket
+ const bitbucketKey = 'bitbucket';
+ const repositoryKey = 'repoKey';
+ const repositorySlug = 'repoSlug';
+ wrapper.setState({ formData: { key: bitbucketKey, repositoryKey, repositorySlug } });
+ wrapper.instance().handleSubmit();
+ await waitAndUpdate(wrapper);
+
+ expect(setProjectBitbucketBinding).toBeCalledWith({
+ almSetting: bitbucketKey,
+ project: PROJECT_KEY,
+ repositoryKey,
+ repositorySlug
+ });
+ expect(wrapper.state().hasBinding).toBe(true);
+ expect(wrapper.state().success).toBe(true);
});
it('should handle failures gracefully', async () => {
expect(wrapper.instance().validateForm({ key: '', repository: 'c' })).toBe(false);
wrapper.setState({
- instances: [{ key: 'azure', alm: ALM_KEYS.AZURE }, { key: 'github', alm: ALM_KEYS.GITHUB }]
+ instances: [
+ { key: 'azure', alm: ALM_KEYS.AZURE },
+ { key: 'bitbucket', alm: ALM_KEYS.BITBUCKET },
+ { key: 'github', alm: ALM_KEYS.GITHUB }
+ ]
});
expect(wrapper.instance().validateForm({ key: 'azure' })).toBe(true);
+
expect(wrapper.instance().validateForm({ key: 'github', repository: '' })).toBe(false);
expect(wrapper.instance().validateForm({ key: 'github', repository: 'asdf' })).toBe(true);
+
+ expect(wrapper.instance().validateForm({ key: 'bitbucket', repositoryKey: 'key' })).toBe(false);
+ expect(
+ wrapper
+ .instance()
+ .validateForm({ key: 'bitbucket', repositoryKey: 'key', repositorySlug: 'slug' })
+ ).toBe(true);
});
function shallowRender(props: Partial<PRDecorationBinding['props']> = {}) {
className="form-field"
>
<label
+ className="display-flex-center"
htmlFor="repository"
>
settings.pr_decoration.binding.form.repository
>
*
</em>
+ <HelpTooltip
+ overlay={
+ <FormattedMessage
+ defaultMessage="settings.pr_decoration.binding.form.repository.help"
+ id="settings.pr_decoration.binding.form.repository.help"
+ values={
+ Object {
+ "example": "SonarSource/sonarqube",
+ }
+ }
+ />
+ }
+ placement="right"
+ />
</label>
<input
className="input-super-large"
personalAccessToken: string;
url: string;
}
-
+
export interface GithubBindingDefinition extends AlmSettingsBinding {
appId: string;
privateKey: string;
url: string;
}
-
-
export interface ProjectAlmBinding {
key: string;
repository?: string;
+ repositoryKey?: string;
+ repositorySlug?: string;
}
export interface AzureProjectAlmBinding {
project: string;
}
+ export interface BitbucketProjectAlmBinding {
+ almSetting: string;
+ project: string;
+ repositoryKey: string;
+ repositorySlug: string;
+ }
+
export interface GithubProjectAlmBinding {
almSetting: string;
project: string;
settings.pr_decoration.binding.form.name=Configuration name
settings.pr_decoration.binding.form.repository=Repository identifier
settings.pr_decoration.binding.form.repository.help=This is the path of your repository. Example: {example}
+settings.pr_decoration.binding.form.repository_key=Project Key
+settings.pr_decoration.binding.form.repository_key.help=You can obtain it from the URL of the Bitbucket Server repository page ({example})
+settings.pr_decoration.binding.form.repository_slug=Repository SLUG
+settings.pr_decoration.binding.form.repository_slug.help=You can obtain it from the URL of the Bitbucket Server repository page ({example})
property.category.general=General
property.category.general.email=Email