aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2020-10-20 17:39:33 +0200
committersonartech <sonartech@sonarsource.com>2020-11-04 20:05:48 +0000
commitb06ff168ea25343711b696e3ceabf8cfcb97d094 (patch)
treefa551fe070a546de159b9638b60e108c86a3b4a7 /server/sonar-web
parente083e2e0ff54828b484cf3534330cf8a186e1f24 (diff)
downloadsonarqube-b06ff168ea25343711b696e3ceabf8cfcb97d094.tar.gz
sonarqube-b06ff168ea25343711b696e3ceabf8cfcb97d094.zip
SONAR-13988 Display validation status for GitHub integration
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/alm-settings.ts16
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx123
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx95
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx70
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap473
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap185
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts22
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts12
-rw-r--r--server/sonar-web/src/main/js/types/alm-settings.ts7
31 files changed, 1169 insertions, 159 deletions
diff --git a/server/sonar-web/src/main/js/api/alm-settings.ts b/server/sonar-web/src/main/js/api/alm-settings.ts
index 56691948c8c..aa193c73b05 100644
--- a/server/sonar-web/src/main/js/api/alm-settings.ts
+++ b/server/sonar-web/src/main/js/api/alm-settings.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { getJSON, post } from 'sonar-ui-common/helpers/request';
+import { get, getJSON, HttpStatus, parseError, post } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import {
AlmSettingsBindingDefinitions,
@@ -47,6 +47,20 @@ export function getAlmSettings(project?: string): Promise<AlmSettingsInstance[]>
.catch(throwGlobalError);
}
+export function validateAlmSettings(key: string): Promise<string> {
+ return get('/api/alm_settings/validate', { key })
+ .then(() => {
+ return '';
+ })
+ .catch((response: Response) => {
+ if (response.status === HttpStatus.BadRequest) {
+ return parseError(response);
+ } else {
+ return throwGlobalError(response);
+ }
+ });
+}
+
export function createGithubConfiguration(data: GithubBindingDefinition) {
return post('/api/alm_settings/create_github', data).catch(throwGlobalError);
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
new file mode 100644
index 00000000000..e335ab9ebbe
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionBox.tsx
@@ -0,0 +1,123 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Button } from 'sonar-ui-common/components/controls/buttons';
+import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
+import AlertErrorIcon from 'sonar-ui-common/components/icons/AlertErrorIcon';
+import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon';
+import DeleteIcon from 'sonar-ui-common/components/icons/DeleteIcon';
+import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { AlmBindingDefinition, AlmSettingsBindingStatus } from '../../../../types/alm-settings';
+
+export interface AlmBindingDefinitionBoxProps {
+ definition: AlmBindingDefinition;
+ multipleDefinitions: boolean;
+ onCheck: (definitionKey: string) => void;
+ onDelete: (definitionKey: string) => void;
+ onEdit: (definitionKey: string) => void;
+ status?: AlmSettingsBindingStatus;
+}
+
+const DEFAULT_STATUS = { alert: false, errorMessage: '', validating: true };
+
+function getImportFeatureStatus(multipleDefinitions: boolean, error: boolean) {
+ if (multipleDefinitions) {
+ return (
+ <div className="display-inline-flex-center">
+ <strong className="spacer-left">
+ {translate('settings.almintegration.feature.alm_repo_import.disabled')}
+ </strong>
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay={translate('settings.almintegration.feature.alm_repo_import.help')}
+ />
+ </div>
+ );
+ } else {
+ return error ? (
+ <AlertErrorIcon className="spacer-left" />
+ ) : (
+ <AlertSuccessIcon className="spacer-left" />
+ );
+ }
+}
+
+export default function AlmBindingDefinitionBox(props: AlmBindingDefinitionBoxProps) {
+ const { definition, multipleDefinitions, status = DEFAULT_STATUS } = props;
+
+ return (
+ <div className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition">
+ <div className="actions pull-right">
+ <Button onClick={() => props.onEdit(definition.key)}>
+ <EditIcon className="spacer-right" />
+ {translate('edit')}
+ </Button>
+ <Button className="button-red spacer-left" onClick={() => props.onDelete(definition.key)}>
+ <DeleteIcon className="spacer-right" />
+ {translate('delete')}
+ </Button>
+ </div>
+
+ <div className="big-spacer-bottom">
+ <h3>{definition.key}</h3>
+ {definition.url && <span>{definition.url}</span>}
+ </div>
+
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i className="deferred-spinner spacer-right" />
+ {translate('settings.almintegration.checking_configuration')}
+ </div>
+ }
+ loading={status.validating}>
+ <div className="display-flex-row spacer-bottom">
+ <div className="huge-spacer-right">
+ {translate('settings.almintegration.feature.pr_decoration.title')}
+ {status.errorMessage ? (
+ <AlertErrorIcon className="spacer-left" />
+ ) : (
+ <AlertSuccessIcon className="spacer-left" />
+ )}
+ </div>
+ <div>
+ {translate('settings.almintegration.feature.alm_repo_import.title')}
+ {getImportFeatureStatus(multipleDefinitions, Boolean(status.errorMessage))}
+ </div>
+ </div>
+
+ {status.alert && (
+ <div className="width-50">
+ <Alert variant={status.errorMessage ? 'error' : 'success'}>
+ {status.errorMessage || translate('settings.almintegration.configuration_valid')}
+ </Alert>
+ </div>
+ )}
+
+ <Button className="big-spacer-top" onClick={() => props.onCheck(definition.key)}>
+ {translate('settings.almintegration.check_configuration')}
+ </Button>
+ </DeferredSpinner>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
index 247abe17d20..a985b48f733 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
@@ -21,11 +21,18 @@ import * as React from 'react';
import {
countBindedProjects,
deleteConfiguration,
- getAlmDefinitions
+ getAlmDefinitions,
+ validateAlmSettings
} from '../../../../api/alm-settings';
import { withAppState } from '../../../../components/hoc/withAppState';
-import { AlmKeys, AlmSettingsBindingDefinitions } from '../../../../types/alm-settings';
+import {
+ AlmBindingDefinition,
+ AlmKeys,
+ AlmSettingsBindingDefinitions,
+ AlmSettingsBindingStatus
+} from '../../../../types/alm-settings';
import AlmIntegrationRenderer from './AlmIntegrationRenderer';
+import { VALIDATED_ALMS } from './utils';
interface Props {
appState: Pick<T.AppState, 'branchesEnabled' | 'multipleAlmEnabled'>;
@@ -36,6 +43,7 @@ interface State {
currentAlm: AlmKeys;
definitionKeyForDeletion?: string;
definitions: AlmSettingsBindingDefinitions;
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
projectCount?: number;
@@ -51,13 +59,23 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
[AlmKeys.GitHub]: [],
[AlmKeys.GitLab]: []
},
+ definitionStatus: {},
loadingAlmDefinitions: true,
loadingProjectCount: false
};
componentDidMount() {
this.mounted = true;
- this.fetchPullRequestDecorationSetting();
+ return this.fetchPullRequestDecorationSetting().then(definitions => {
+ if (definitions) {
+ // Validate all alms on load:
+ VALIDATED_ALMS.forEach(alm => {
+ this.state.definitions[alm].forEach((def: AlmBindingDefinition) =>
+ this.handleCheck(def.key, false)
+ );
+ });
+ }
+ });
}
componentWillUnmount() {
@@ -91,7 +109,9 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
definitions,
loadingAlmDefinitions: false
});
+ return definitions;
}
+ return undefined;
})
.catch(() => {
if (this.mounted) {
@@ -127,6 +147,31 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
});
};
+ handleCheck = async (definitionKey: string, alertSuccess = true) => {
+ this.setState(({ definitionStatus }) => {
+ definitionStatus[definitionKey] = {
+ ...definitionStatus[definitionKey],
+ validating: true
+ };
+
+ return { definitionStatus: { ...definitionStatus } };
+ });
+
+ const errorMessage = await validateAlmSettings(definitionKey).catch(() => undefined);
+
+ if (this.mounted && errorMessage !== undefined) {
+ this.setState(({ definitionStatus }) => {
+ definitionStatus[definitionKey] = {
+ alert: alertSuccess || Boolean(errorMessage),
+ errorMessage,
+ validating: false
+ };
+
+ return { definitionStatus: { ...definitionStatus } };
+ });
+ }
+ };
+
render() {
const {
appState: { branchesEnabled, multipleAlmEnabled },
@@ -139,6 +184,7 @@ export class AlmIntegration extends React.PureComponent<Props, State> {
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onCancel={this.handleCancel}
onConfirmDelete={this.deleteConfiguration}
+ onCheck={this.handleCheck}
onDelete={this.handleDelete}
onSelectAlm={this.handleSelectAlm}
onUpdateDefinitions={this.fetchPullRequestDecorationSetting}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
index 16e8f95b1e5..5d4c9ce2e66 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
@@ -21,7 +21,11 @@ import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
-import { AlmKeys, AlmSettingsBindingDefinitions } from '../../../../types/alm-settings';
+import {
+ AlmKeys,
+ AlmSettingsBindingDefinitions,
+ AlmSettingsBindingStatus
+} from '../../../../types/alm-settings';
import AzureTab from './AzureTab';
import BitbucketTab from './BitbucketTab';
import DeleteModal from './DeleteModal';
@@ -34,10 +38,12 @@ export interface AlmIntegrationRendererProps {
currentAlm: AlmKeys;
definitionKeyForDeletion?: string;
definitions: AlmSettingsBindingDefinitions;
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
+ onCheck: (definitionKey: string) => void;
onConfirmDelete: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onSelectAlm: (alm: AlmKeys) => void;
@@ -111,6 +117,7 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
component,
definitionKeyForDeletion,
definitions,
+ definitionStatus,
currentAlm,
loadingAlmDefinitions,
loadingProjectCount,
@@ -137,9 +144,11 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
{currentAlm === AlmKeys.Azure && (
<AzureTab
definitions={definitions.azure}
+ definitionStatus={definitionStatus}
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
@@ -147,9 +156,11 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
{currentAlm === AlmKeys.Bitbucket && (
<BitbucketTab
definitions={definitions.bitbucket}
+ definitionStatus={definitionStatus}
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
@@ -159,9 +170,11 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
branchesEnabled={branchesEnabled}
component={component}
definitions={definitions.github}
+ definitionStatus={definitionStatus}
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
@@ -170,9 +183,11 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
<GitlabTab
branchesEnabled={branchesEnabled}
definitions={definitions.gitlab}
+ definitionStatus={definitionStatus}
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
index ff6783180ab..d603bd64027 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
@@ -18,7 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { AlmBindingDefinition, AlmKeys } from '../../../../types/alm-settings';
+import {
+ AlmBindingDefinition,
+ AlmKeys,
+ AlmSettingsBindingStatus
+} from '../../../../types/alm-settings';
import { AlmBindingDefinitionFormChildrenProps } from './AlmBindingDefinitionForm';
import { AlmIntegrationFeatureBoxProps } from './AlmIntegrationFeatureBox';
import AlmTabRenderer from './AlmTabRenderer';
@@ -31,12 +35,14 @@ interface Props<B> {
createConfiguration: (data: B) => Promise<void>;
defaultBinding: B;
definitions: B[];
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
features?: AlmIntegrationFeatureBoxProps[];
form: (props: AlmBindingDefinitionFormChildrenProps<B>) => React.ReactNode;
help?: React.ReactNode;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
+ onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
optionalFields?: Array<keyof B>;
@@ -85,7 +91,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
? this.props.updateConfiguration({ newKey: config.key, ...config, key: originalKey })
: // If there's no support for multi-ALM binding, the key will be an empty string.
// Set a default.
- this.props.createConfiguration(config.key ? config : { ...config, key: this.props.alm });
+ this.props.createConfiguration({ ...config, key: config.key || this.props.alm });
this.setState({ submitting: true });
return call
@@ -95,6 +101,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
}
})
.then(this.props.onUpdateDefinitions)
+ .then(() => this.props.onCheck(config.key))
.catch(() => {
if (this.mounted) {
this.setState({ submitting: false, success: false });
@@ -110,6 +117,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
alm,
defaultBinding,
definitions,
+ definitionStatus,
features,
form,
help,
@@ -128,6 +136,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
alm={alm}
defaultBinding={defaultBinding}
definitions={definitions}
+ definitionStatus={definitionStatus}
editedDefinition={editedDefinition}
features={features}
form={form}
@@ -136,6 +145,7 @@ export default class AlmTab<B extends AlmBindingDefinition> extends React.PureCo
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
onCancel={this.handleCancel}
+ onCheck={this.props.onCheck}
onCreate={this.handleCreate}
onDelete={this.props.onDelete}
onEdit={this.handleEdit}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
index 7e946452002..2ed43e96d57 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
@@ -20,9 +20,15 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import { Button } from 'sonar-ui-common/components/controls/buttons';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { AlmBindingDefinition, AlmKeys } from '../../../../types/alm-settings';
+import {
+ AlmBindingDefinition,
+ AlmKeys,
+ AlmSettingsBindingStatus
+} from '../../../../types/alm-settings';
+import AlmBindingDefinitionBox from './AlmBindingDefinitionBox';
import AlmBindingDefinitionForm, {
AlmBindingDefinitionFormChildrenProps
} from './AlmBindingDefinitionForm';
@@ -30,12 +36,14 @@ import AlmBindingDefinitionsTable from './AlmBindingDefinitionsTable';
import AlmIntegrationFeatureBox, {
AlmIntegrationFeatureBoxProps
} from './AlmIntegrationFeatureBox';
+import { VALIDATED_ALMS } from './utils';
export interface AlmTabRendererProps<B> {
additionalColumnsHeaders: string[];
additionalColumnsKeys: Array<keyof B>;
additionalTableInfo?: React.ReactNode;
alm: AlmKeys;
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
editedDefinition?: B;
defaultBinding: B;
definitions: B[];
@@ -46,6 +54,7 @@ export interface AlmTabRendererProps<B> {
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
+ onCheck: (definitionKey: string) => void;
onCreate: () => void;
onDelete: (definitionKey: string) => void;
onEdit: (definitionKey: string) => void;
@@ -55,14 +64,70 @@ export interface AlmTabRendererProps<B> {
success: boolean;
}
-export default function AlmTabRenderer<B extends AlmBindingDefinition>(
- props: AlmTabRendererProps<B>
-) {
+function renderListOfDefinitions<B extends AlmBindingDefinition>(props: AlmTabRendererProps<B>) {
const {
additionalColumnsHeaders,
additionalColumnsKeys,
additionalTableInfo,
alm,
+ definitions,
+ definitionStatus,
+ loadingProjectCount
+ } = props;
+
+ if (VALIDATED_ALMS.includes(alm)) {
+ return (
+ <>
+ <div className="spacer-bottom text-right">
+ <Button
+ data-test="settings__alm-create"
+ disabled={loadingProjectCount}
+ onClick={props.onCreate}>
+ {translate('settings.almintegration.table.create')}
+ </Button>
+ </div>
+ {definitions.map(def => (
+ <AlmBindingDefinitionBox
+ definition={def}
+ key={def.key}
+ multipleDefinitions={definitions.length > 1}
+ onCheck={props.onCheck}
+ onDelete={props.onDelete}
+ onEdit={props.onEdit}
+ status={definitionStatus[def.key]}
+ />
+ ))}
+ </>
+ );
+ }
+
+ const mappedDefinitions = definitions.map(({ key, ...properties }) => {
+ const additionalColumns = additionalColumnsKeys.map(k => (properties as any)[k]);
+ return {
+ key,
+ additionalColumns
+ };
+ });
+
+ return (
+ <AlmBindingDefinitionsTable
+ additionalColumnsHeaders={additionalColumnsHeaders}
+ additionalTableInfo={additionalTableInfo}
+ alm={alm}
+ definitions={mappedDefinitions}
+ loading={loadingProjectCount}
+ onCreate={props.onCreate}
+ onDelete={props.onDelete}
+ onEdit={props.onEdit}
+ />
+ );
+}
+
+export default function AlmTabRenderer<B extends AlmBindingDefinition>(
+ props: AlmTabRendererProps<B>
+) {
+ const {
+ alm,
defaultBinding,
definitions,
editedDefinition,
@@ -90,7 +155,6 @@ export default function AlmTabRenderer<B extends AlmBindingDefinition>(
} = props;
let definition: B | undefined;
- let mappedDefinitions: Array<{ key: string; additionalColumns: string[] }> = [];
let showEdit: boolean | undefined;
if (!multipleAlmEnabled) {
@@ -99,30 +163,13 @@ export default function AlmTabRenderer<B extends AlmBindingDefinition>(
definition = definitions[0];
}
showEdit = definition && editedDefinition === undefined;
- } else {
- mappedDefinitions = definitions.map(({ key, ...properties }) => {
- const additionalColumns = additionalColumnsKeys.map(k => (properties as any)[k]);
- return {
- key,
- additionalColumns
- };
- });
}
return (
<div className="big-padded">
{multipleAlmEnabled ? (
<DeferredSpinner loading={loadingAlmDefinitions}>
- <AlmBindingDefinitionsTable
- additionalColumnsHeaders={additionalColumnsHeaders}
- additionalTableInfo={additionalTableInfo}
- alm={alm}
- definitions={mappedDefinitions}
- loading={loadingProjectCount}
- onCreate={props.onCreate}
- onDelete={props.onDelete}
- onEdit={props.onEdit}
- />
+ {renderListOfDefinitions(props)}
{editedDefinition && (
<AlmBindingDefinitionForm
@@ -153,7 +200,7 @@ export default function AlmTabRenderer<B extends AlmBindingDefinition>(
</AlmBindingDefinitionForm>
)}
- {features.length > 0 && (
+ {!VALIDATED_ALMS.includes(alm) && features.length > 0 && (
<div className="big-spacer-top big-padded-top bordered-top">
<h3 className="big-spacer-bottom">{translate('settings.almintegration.features')}</h3>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx
index b2d211ba787..f727d695c2d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx
@@ -20,21 +20,33 @@
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createAzureConfiguration, updateAzureConfiguration } from '../../../../api/alm-settings';
-import { AlmKeys, AzureBindingDefinition } from '../../../../types/alm-settings';
+import {
+ AlmKeys,
+ AlmSettingsBindingStatus,
+ AzureBindingDefinition
+} from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import AzureForm from './AzureForm';
export interface AzureTabProps {
definitions: AzureBindingDefinition[];
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
+ onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}
export default function AzureTab(props: AzureTabProps) {
- const { multipleAlmEnabled, definitions, loadingAlmDefinitions, loadingProjectCount } = props;
+ const {
+ multipleAlmEnabled,
+ definitions,
+ definitionStatus,
+ loadingAlmDefinitions,
+ loadingProjectCount
+ } = props;
return (
<div className="bordered">
@@ -43,6 +55,7 @@ export default function AzureTab(props: AzureTabProps) {
createConfiguration={createAzureConfiguration}
defaultBinding={{ key: '', personalAccessToken: '' }}
definitions={definitions}
+ definitionStatus={definitionStatus}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
@@ -55,6 +68,7 @@ export default function AzureTab(props: AzureTabProps) {
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateAzureConfiguration}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
index 05010d67d83..4fc261cfb92 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
@@ -26,21 +26,33 @@ import {
createBitbucketConfiguration,
updateBitbucketConfiguration
} from '../../../../api/alm-settings';
-import { AlmKeys, BitbucketBindingDefinition } from '../../../../types/alm-settings';
+import {
+ AlmKeys,
+ AlmSettingsBindingStatus,
+ BitbucketBindingDefinition
+} from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import BitbucketForm from './BitbucketForm';
export interface BitbucketTabProps {
definitions: BitbucketBindingDefinition[];
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
+ onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}
export default function BitbucketTab(props: BitbucketTabProps) {
- const { multipleAlmEnabled, definitions, loadingAlmDefinitions, loadingProjectCount } = props;
+ const {
+ multipleAlmEnabled,
+ definitions,
+ definitionStatus,
+ loadingAlmDefinitions,
+ loadingProjectCount
+ } = props;
return (
<div className="bordered">
@@ -66,6 +78,7 @@ export default function BitbucketTab(props: BitbucketTabProps) {
createConfiguration={createBitbucketConfiguration}
defaultBinding={{ key: '', url: '', personalAccessToken: '' }}
definitions={definitions}
+ definitionStatus={definitionStatus}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
@@ -107,6 +120,7 @@ export default function BitbucketTab(props: BitbucketTabProps) {
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateBitbucketConfiguration}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx
index 82f4298b289..a44d2e8813e 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx
@@ -23,7 +23,11 @@ import WarningIcon from 'sonar-ui-common/components/icons/WarningIcon';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { createGithubConfiguration, updateGithubConfiguration } from '../../../../api/alm-settings';
-import { AlmKeys, GithubBindingDefinition } from '../../../../types/alm-settings';
+import {
+ AlmKeys,
+ AlmSettingsBindingStatus,
+ GithubBindingDefinition
+} from '../../../../types/alm-settings';
import { ALM_INTEGRATION } from '../AdditionalCategoryKeys';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
import AlmTab from './AlmTab';
@@ -33,9 +37,11 @@ export interface GithubTabProps {
branchesEnabled: boolean;
component?: T.Component;
definitions: GithubBindingDefinition[];
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
+ onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}
@@ -46,6 +52,7 @@ export default function GithubTab(props: GithubTabProps) {
component,
multipleAlmEnabled,
definitions,
+ definitionStatus,
loadingAlmDefinitions,
loadingProjectCount
} = props;
@@ -86,6 +93,7 @@ export default function GithubTab(props: GithubTabProps) {
privateKey: ''
}}
definitions={definitions}
+ definitionStatus={definitionStatus}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
@@ -129,6 +137,7 @@ export default function GithubTab(props: GithubTabProps) {
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGithubConfiguration}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx
index a50d7809bde..db7558480d0 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx
@@ -22,7 +22,11 @@ import { FormattedMessage } from 'react-intl';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { createGitlabConfiguration, updateGitlabConfiguration } from '../../../../api/alm-settings';
-import { AlmKeys, GitlabBindingDefinition } from '../../../../types/alm-settings';
+import {
+ AlmKeys,
+ AlmSettingsBindingStatus,
+ GitlabBindingDefinition
+} from '../../../../types/alm-settings';
import { ALM_INTEGRATION } from '../AdditionalCategoryKeys';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
import AlmTab from './AlmTab';
@@ -32,9 +36,11 @@ export interface GitlabTabProps {
branchesEnabled: boolean;
component?: T.Component;
definitions: GitlabBindingDefinition[];
+ definitionStatus: T.Dict<AlmSettingsBindingStatus>;
loadingAlmDefinitions: boolean;
loadingProjectCount: boolean;
multipleAlmEnabled: boolean;
+ onCheck: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}
@@ -45,6 +51,7 @@ export default function GitlabTab(props: GitlabTabProps) {
component,
multipleAlmEnabled,
definitions,
+ definitionStatus,
loadingAlmDefinitions,
loadingProjectCount
} = props;
@@ -79,6 +86,7 @@ export default function GitlabTab(props: GitlabTabProps) {
createConfiguration={createGitlabConfiguration}
defaultBinding={{ key: '', personalAccessToken: '', url: '' }}
definitions={definitions}
+ definitionStatus={definitionStatus}
features={[
{
name: translate('settings.almintegration.feature.mr_decoration.title'),
@@ -107,6 +115,7 @@ export default function GitlabTab(props: GitlabTabProps) {
loadingAlmDefinitions={loadingAlmDefinitions}
loadingProjectCount={loadingProjectCount}
multipleAlmEnabled={multipleAlmEnabled}
+ onCheck={props.onCheck}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGitlabConfiguration}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx
new file mode 100644
index 00000000000..d8c9cbb93d4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionBox-test.tsx
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+ mockAlmSettingsBindingStatus,
+ mockGithubBindingDefinition
+} from '../../../../../helpers/mocks/alm-settings';
+import AlmBindingDefinitionBox, { AlmBindingDefinitionBoxProps } from '../AlmBindingDefinitionBox';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ multipleDefinitions: true })).toMatchSnapshot('multiple definitions');
+ expect(
+ shallowRender({ status: mockAlmSettingsBindingStatus({ errorMessage: '', validating: false }) })
+ ).toMatchSnapshot('success');
+ expect(
+ shallowRender({
+ status: mockAlmSettingsBindingStatus({
+ errorMessage: 'Oops, something went wrong',
+ validating: false
+ })
+ })
+ ).toMatchSnapshot('error');
+
+ expect(
+ shallowRender({
+ status: mockAlmSettingsBindingStatus({ alert: true, errorMessage: '', validating: false })
+ })
+ ).toMatchSnapshot('success with alert');
+ expect(
+ shallowRender({
+ status: mockAlmSettingsBindingStatus({
+ alert: true,
+ errorMessage: 'Oops, something went wrong',
+ validating: false
+ })
+ })
+ ).toMatchSnapshot('error with alert');
+});
+
+function shallowRender(props: Partial<AlmBindingDefinitionBoxProps> = {}) {
+ return shallow(
+ <AlmBindingDefinitionBox
+ definition={mockGithubBindingDefinition()}
+ multipleDefinitions={false}
+ onCheck={jest.fn()}
+ onDelete={jest.fn()}
+ onEdit={jest.fn()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
index 4940b4370ab..d165dbaa9af 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
@@ -23,7 +23,8 @@ import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import {
countBindedProjects,
deleteConfiguration,
- getAlmDefinitions
+ getAlmDefinitions,
+ validateAlmSettings
} from '../../../../../api/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import { AlmIntegration } from '../AlmIntegration';
@@ -31,7 +32,8 @@ import { AlmIntegration } from '../AlmIntegration';
jest.mock('../../../../../api/alm-settings', () => ({
countBindedProjects: jest.fn().mockResolvedValue(0),
deleteConfiguration: jest.fn().mockResolvedValue(undefined),
- getAlmDefinitions: jest.fn().mockResolvedValue({ github: [] })
+ getAlmDefinitions: jest.fn().mockResolvedValue({ github: [] }),
+ validateAlmSettings: jest.fn().mockResolvedValue('')
}));
beforeEach(() => {
@@ -42,6 +44,23 @@ it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
+it('should validate existing configurations', async () => {
+ (getAlmDefinitions as jest.Mock).mockResolvedValueOnce({
+ [AlmKeys.Azure]: [{ key: 'a1' }],
+ [AlmKeys.Bitbucket]: [{ key: 'b1' }],
+ [AlmKeys.GitHub]: [{ key: 'gh1' }, { key: 'gh2' }],
+ [AlmKeys.GitLab]: [{ key: 'gl1' }]
+ });
+
+ const wrapper = shallowRender();
+
+ await waitAndUpdate(wrapper);
+
+ expect(validateAlmSettings).toBeCalledTimes(2);
+ expect(validateAlmSettings).toBeCalledWith('gh1');
+ expect(validateAlmSettings).toBeCalledWith('gh2');
+});
+
it('should handle alm selection', async () => {
const wrapper = shallowRender();
@@ -76,17 +95,51 @@ it('should delete configuration', async () => {
expect(wrapper.state().definitionKeyForDeletion).toBeUndefined();
});
+it('should validate a configuration', async () => {
+ const definitionKey = 'validated-key';
+ const errorMessage = 'an error occured';
+
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+
+ (validateAlmSettings as jest.Mock)
+ .mockResolvedValueOnce(undefined)
+ .mockResolvedValueOnce(errorMessage)
+ .mockResolvedValueOnce('');
+
+ await wrapper.instance().handleCheck(definitionKey);
+
+ expect(wrapper.state().definitionStatus[definitionKey]).toEqual(
+ expect.objectContaining({
+ validating: true
+ })
+ );
+
+ await wrapper.instance().handleCheck(definitionKey);
+
+ expect(wrapper.state().definitionStatus[definitionKey]).toEqual({
+ alert: true,
+ errorMessage,
+ validating: false
+ });
+
+ await wrapper.instance().handleCheck(definitionKey);
+
+ expect(wrapper.state().definitionStatus[definitionKey]).toEqual({
+ alert: true,
+ errorMessage: '',
+ validating: false
+ });
+});
+
it('should fetch settings', async () => {
const wrapper = shallowRender();
- await wrapper
- .instance()
- .fetchPullRequestDecorationSetting()
- .then(() => {
- expect(getAlmDefinitions).toBeCalled();
- expect(wrapper.state().definitions).toEqual({ github: [] });
- expect(wrapper.state().loadingAlmDefinitions).toBe(false);
- });
+ await wrapper.instance().fetchPullRequestDecorationSetting();
+
+ expect(getAlmDefinitions).toBeCalled();
+ expect(wrapper.state().definitions).toEqual({ github: [] });
+ expect(wrapper.state().loadingAlmDefinitions).toBe(false);
});
function shallowRender() {
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
index c23e364d9c4..fff40776838 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
@@ -41,10 +41,12 @@ function shallowRender(props: Partial<AlmIntegrationRendererProps> = {}) {
branchesEnabled={true}
currentAlm={AlmKeys.GitHub}
definitions={{ azure: [], bitbucket: [], github: [], gitlab: [] }}
+ definitionStatus={{}}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
onCancel={jest.fn()}
+ onCheck={jest.fn()}
onConfirmDelete={jest.fn()}
onDelete={jest.fn()}
onSelectAlm={jest.fn()}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx
index 2b1a6c45835..acdc39b415a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx
@@ -26,7 +26,8 @@ import AlmTab from '../AlmTab';
const DEFAULT_BINDING = {
key: '',
- personalAccessToken: ''
+ personalAccessToken: '',
+ url: undefined
};
it('should render correctly', () => {
@@ -97,10 +98,12 @@ function shallowRender(props: Partial<AlmTab<AzureBindingDefinition>['props']> =
createConfiguration={jest.fn()}
defaultBinding={DEFAULT_BINDING}
definitions={[mockAzureBindingDefinition()]}
+ definitionStatus={{}}
form={jest.fn()}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
updateConfiguration={jest.fn()}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
index 63b1a2b0258..6f352ed8489 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
@@ -19,20 +19,31 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { mockGithubBindingDefinition } from '../../../../../helpers/mocks/alm-settings';
-import { AlmKeys, GithubBindingDefinition } from '../../../../../types/alm-settings';
+import {
+ mockAzureBindingDefinition,
+ mockGithubBindingDefinition
+} from '../../../../../helpers/mocks/alm-settings';
+import {
+ AlmBindingDefinition,
+ AlmKeys,
+ AzureBindingDefinition
+} from '../../../../../types/alm-settings';
import AlmTabRenderer, { AlmTabRendererProps } from '../AlmTabRenderer';
it('should render correctly for multi-ALM binding', () => {
- expect(shallowRender({ loadingAlmDefinitions: true })).toMatchSnapshot('loading ALM definitions');
- expect(shallowRender({ loadingProjectCount: true })).toMatchSnapshot('loading project count');
- expect(shallowRender({ submitting: true })).toMatchSnapshot('submitting');
- expect(shallowRender()).toMatchSnapshot('loaded');
- expect(shallowRender({ editedDefinition: mockGithubBindingDefinition() })).toMatchSnapshot(
+ expect(shallowRenderAzure({ loadingAlmDefinitions: true })).toMatchSnapshot(
+ 'loading ALM definitions'
+ );
+ expect(shallowRenderAzure({ loadingProjectCount: true })).toMatchSnapshot(
+ 'loading project count'
+ );
+ expect(shallowRenderAzure({ submitting: true })).toMatchSnapshot('submitting');
+ expect(shallowRenderAzure()).toMatchSnapshot('loaded');
+ expect(shallowRenderAzure({ editedDefinition: mockAzureBindingDefinition() })).toMatchSnapshot(
'editing a definition'
);
expect(
- shallowRender({
+ shallowRenderAzure({
features: [
{
active: true,
@@ -51,27 +62,49 @@ it('should render correctly for multi-ALM binding', () => {
it('should render correctly for single-ALM binding', () => {
expect(
- shallowRender({ loadingAlmDefinitions: true, multipleAlmEnabled: false })
+ shallowRenderAzure({ loadingAlmDefinitions: true, multipleAlmEnabled: false })
).toMatchSnapshot();
- expect(shallowRender({ multipleAlmEnabled: false })).toMatchSnapshot();
+ expect(shallowRenderAzure({ multipleAlmEnabled: false })).toMatchSnapshot();
expect(
- shallowRender({ definitions: [mockGithubBindingDefinition()], multipleAlmEnabled: false })
+ shallowRenderAzure({ definitions: [mockAzureBindingDefinition()], multipleAlmEnabled: false })
).toMatchSnapshot();
});
-function shallowRender(props: Partial<AlmTabRendererProps<GithubBindingDefinition>> = {}) {
+it('should render correctly with validation', () => {
+ const githubProps = {
+ alm: AlmKeys.GitHub,
+ defaultBinding: mockGithubBindingDefinition(),
+ definitions: [mockGithubBindingDefinition()]
+ };
+ expect(shallowRender(githubProps)).toMatchSnapshot();
+ expect(shallowRender({ ...githubProps, definitions: [] })).toMatchSnapshot('empty');
+});
+
+function shallowRenderAzure(props: Partial<AlmTabRendererProps<AzureBindingDefinition>> = {}) {
+ return shallowRender({
+ defaultBinding: mockAzureBindingDefinition(),
+ definitions: [mockAzureBindingDefinition()],
+ ...props
+ });
+}
+
+function shallowRender<B extends AlmBindingDefinition>(
+ props: Partial<AlmTabRendererProps<B>> = {}
+) {
return shallow(
<AlmTabRenderer
- additionalColumnsHeaders={['url', 'app_id']}
- additionalColumnsKeys={['url', 'appId']}
- alm={AlmKeys.GitHub}
- defaultBinding={mockGithubBindingDefinition()}
- definitions={[mockGithubBindingDefinition()]}
+ additionalColumnsHeaders={[]}
+ additionalColumnsKeys={[]}
+ alm={AlmKeys.Azure}
+ defaultBinding={{} as any}
+ definitions={[]}
+ definitionStatus={{}}
form={jest.fn()}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={jest.fn()}
+ onCheck={jest.fn()}
onCreate={jest.fn()}
onDelete={jest.fn()}
onEdit={jest.fn()}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx
index ef7fe85ee3d..a2b0d7ce073 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx
@@ -30,9 +30,11 @@ function shallowRender(props: Partial<AzureTabProps> = {}) {
return shallow(
<AzureTab
definitions={[mockAzureBindingDefinition()]}
+ definitionStatus={{}}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
index 6f2854c2eef..ee9acbec452 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
@@ -30,9 +30,11 @@ function shallowRender(props: Partial<BitbucketTabProps> = {}) {
return shallow(
<BitbucketTab
definitions={[mockBitbucketBindingDefinition()]}
+ definitionStatus={{}}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx
index 8aca94e7bd8..11e94059617 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx
@@ -32,9 +32,11 @@ function shallowRender(props: Partial<GithubTabProps> = {}) {
<GithubTab
branchesEnabled={true}
definitions={[mockGithubBindingDefinition()]}
+ definitionStatus={{}}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx
index cd3a792d8c1..f6461a33810 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx
@@ -42,9 +42,11 @@ function shallowRender(props: Partial<GitlabTabProps> = {}) {
<GitlabTab
branchesEnabled={true}
definitions={[mockGitlabBindingDefinition()]}
+ definitionStatus={{}}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={jest.fn()}
onDelete={jest.fn()}
onUpdateDefinitions={jest.fn()}
{...props}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
new file mode 100644
index 00000000000..0629f4af3c2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionBox-test.tsx.snap
@@ -0,0 +1,473 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={true}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: error 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={false}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertErrorIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <AlertErrorIcon
+ className="spacer-left"
+ />
+ </div>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: error with alert 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={false}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertErrorIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <AlertErrorIcon
+ className="spacer-left"
+ />
+ </div>
+ </div>
+ <div
+ className="width-50"
+ >
+ <Alert
+ variant="error"
+ >
+ Oops, something went wrong
+ </Alert>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: multiple definitions 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={true}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <div
+ className="display-inline-flex-center"
+ >
+ <strong
+ className="spacer-left"
+ >
+ settings.almintegration.feature.alm_repo_import.disabled
+ </strong>
+ <HelpTooltip
+ className="little-spacer-left"
+ overlay="settings.almintegration.feature.alm_repo_import.help"
+ />
+ </div>
+ </div>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: success 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={false}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly: success with alert 1`] = `
+<div
+ className="boxed-group-inner bordered spacer-top spacer-bottom it__alm-binding-definition"
+>
+ <div
+ className="actions pull-right"
+ >
+ <Button
+ onClick={[Function]}
+ >
+ <EditIcon
+ className="spacer-right"
+ />
+ edit
+ </Button>
+ <Button
+ className="button-red spacer-left"
+ onClick={[Function]}
+ >
+ <DeleteIcon
+ className="spacer-right"
+ />
+ delete
+ </Button>
+ </div>
+ <div
+ className="big-spacer-bottom"
+ >
+ <h3>
+ key
+ </h3>
+ <span>
+ http://github.enterprise.com
+ </span>
+ </div>
+ <DeferredSpinner
+ customSpinner={
+ <div>
+ <i
+ className="deferred-spinner spacer-right"
+ />
+ settings.almintegration.checking_configuration
+ </div>
+ }
+ loading={false}
+ >
+ <div
+ className="display-flex-row spacer-bottom"
+ >
+ <div
+ className="huge-spacer-right"
+ >
+ settings.almintegration.feature.pr_decoration.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ <div>
+ settings.almintegration.feature.alm_repo_import.title
+ <AlertSuccessIcon
+ className="spacer-left"
+ />
+ </div>
+ </div>
+ <div
+ className="width-50"
+ >
+ <Alert
+ variant="success"
+ >
+ settings.almintegration.configuration_valid
+ </Alert>
+ </div>
+ <Button
+ className="big-spacer-top"
+ onClick={[Function]}
+ >
+ settings.almintegration.check_configuration
+ </Button>
+ </DeferredSpinner>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
index c3ce1fad8ad..647758269cb 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
@@ -4,6 +4,7 @@ exports[`should render correctly 1`] = `
<AlmIntegrationRenderer
branchesEnabled={true}
currentAlm="github"
+ definitionStatus={Object {}}
definitions={
Object {
"azure": Array [],
@@ -16,6 +17,7 @@ exports[`should render correctly 1`] = `
loadingProjectCount={false}
multipleAlmEnabled={false}
onCancel={[Function]}
+ onCheck={[Function]}
onConfirmDelete={[Function]}
onDelete={[Function]}
onSelectAlm={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
index 8be88f64c3b..2a887a2e77c 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
@@ -75,10 +75,12 @@ exports[`should render correctly: azure 1`] = `
}
/>
<AzureTab
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
@@ -160,10 +162,12 @@ exports[`should render correctly: bitbucket 1`] = `
}
/>
<BitbucketTab
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
@@ -246,10 +250,12 @@ exports[`should render correctly: default 1`] = `
/>
<GithubTab
branchesEnabled={true}
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
@@ -332,10 +338,12 @@ exports[`should render correctly: delete modal 1`] = `
/>
<GithubTab
branchesEnabled={true}
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
@@ -423,10 +431,12 @@ exports[`should render correctly: gitlab 1`] = `
/>
<GitlabTab
branchesEnabled={true}
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
@@ -509,10 +519,12 @@ exports[`should render correctly: loading 1`] = `
/>
<GithubTab
branchesEnabled={true}
+ definitionStatus={Object {}}
definitions={Array []}
loadingAlmDefinitions={true}
loadingProjectCount={true}
multipleAlmEnabled={false}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
index e0e95f11057..68d1d8350fa 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
@@ -9,8 +9,10 @@ exports[`should render correctly 1`] = `
Object {
"key": "",
"personalAccessToken": "",
+ "url": undefined,
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -24,6 +26,7 @@ exports[`should render correctly 1`] = `
loadingProjectCount={false}
multipleAlmEnabled={true}
onCancel={[Function]}
+ onCheck={[MockFunction]}
onCreate={[Function]}
onDelete={[MockFunction]}
onEdit={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
index 4ef84a57232..ffed975f0b5 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
@@ -8,20 +8,12 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`]
loading={false}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -34,18 +26,14 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`]
<AlmBindingDefinitionForm
bindingDefinition={
Object {
- "appId": "123456",
- "clientId": "client1",
- "clientSecret": "**clientsecret**",
"key": "key",
- "privateKey": "asdf1234",
- "url": "http://github.enterprise.com",
+ "personalAccessToken": "asdf1234",
}
}
help={
<FormattedMessage
- defaultMessage="settings.almintegration.github.info"
- id="settings.almintegration.github.info"
+ defaultMessage="settings.almintegration.azure.info"
+ id="settings.almintegration.azure.info"
values={
Object {
"link": <Link
@@ -78,20 +66,12 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = `
loading={false}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -113,20 +93,12 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions
loading={true}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -148,20 +120,12 @@ exports[`should render correctly for multi-ALM binding: loading project count 1`
loading={false}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -183,20 +147,12 @@ exports[`should render correctly for multi-ALM binding: submitting 1`] = `
loading={false}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -218,20 +174,12 @@ exports[`should render correctly for multi-ALM binding: with features 1`] = `
loading={false}
>
<AlmBindingDefinitionsTable
- additionalColumnsHeaders={
- Array [
- "url",
- "app_id",
- ]
- }
- alm="github"
+ additionalColumnsHeaders={Array []}
+ alm="azure"
definitions={
Array [
Object {
- "additionalColumns": Array [
- "http://github.enterprise.com",
- "123456",
- ],
+ "additionalColumns": Array [],
"key": "key",
},
]
@@ -277,18 +225,14 @@ exports[`should render correctly for single-ALM binding 1`] = `
<AlmBindingDefinitionForm
bindingDefinition={
Object {
- "appId": "123456",
- "clientId": "client1",
- "clientSecret": "**clientsecret**",
"key": "key",
- "privateKey": "asdf1234",
- "url": "http://github.enterprise.com",
+ "personalAccessToken": "asdf1234",
}
}
help={
<FormattedMessage
- defaultMessage="settings.almintegration.github.info"
- id="settings.almintegration.github.info"
+ defaultMessage="settings.almintegration.azure.info"
+ id="settings.almintegration.azure.info"
values={
Object {
"link": <Link
@@ -324,18 +268,14 @@ exports[`should render correctly for single-ALM binding 2`] = `
<AlmBindingDefinitionForm
bindingDefinition={
Object {
- "appId": "123456",
- "clientId": "client1",
- "clientSecret": "**clientsecret**",
"key": "key",
- "privateKey": "asdf1234",
- "url": "http://github.enterprise.com",
+ "personalAccessToken": "asdf1234",
}
}
help={
<FormattedMessage
- defaultMessage="settings.almintegration.github.info"
- id="settings.almintegration.github.info"
+ defaultMessage="settings.almintegration.azure.info"
+ id="settings.almintegration.azure.info"
values={
Object {
"link": <Link
@@ -371,18 +311,14 @@ exports[`should render correctly for single-ALM binding 3`] = `
<AlmBindingDefinitionForm
bindingDefinition={
Object {
- "appId": "123456",
- "clientId": "client1",
- "clientSecret": "**clientsecret**",
"key": "key",
- "privateKey": "asdf1234",
- "url": "http://github.enterprise.com",
+ "personalAccessToken": "asdf1234",
}
}
help={
<FormattedMessage
- defaultMessage="settings.almintegration.github.info"
- id="settings.almintegration.github.info"
+ defaultMessage="settings.almintegration.azure.info"
+ id="settings.almintegration.azure.info"
values={
Object {
"link": <Link
@@ -410,3 +346,64 @@ exports[`should render correctly for single-ALM binding 3`] = `
</AlmBindingDefinitionForm>
</div>
`;
+
+exports[`should render correctly with validation 1`] = `
+<div
+ className="big-padded"
+>
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="spacer-bottom text-right"
+ >
+ <Button
+ data-test="settings__alm-create"
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ settings.almintegration.table.create
+ </Button>
+ </div>
+ <AlmBindingDefinitionBox
+ definition={
+ Object {
+ "appId": "123456",
+ "clientId": "client1",
+ "clientSecret": "**clientsecret**",
+ "key": "key",
+ "privateKey": "asdf1234",
+ "url": "http://github.enterprise.com",
+ }
+ }
+ key="key"
+ multipleDefinitions={false}
+ onCheck={[MockFunction]}
+ onDelete={[MockFunction]}
+ onEdit={[MockFunction]}
+ />
+ </DeferredSpinner>
+</div>
+`;
+
+exports[`should render correctly with validation: empty 1`] = `
+<div
+ className="big-padded"
+>
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="spacer-bottom text-right"
+ >
+ <Button
+ data-test="settings__alm-create"
+ disabled={false}
+ onClick={[MockFunction]}
+ >
+ settings.almintegration.table.create
+ </Button>
+ </div>
+ </DeferredSpinner>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap
index 35b9a582a23..0ed28e44289 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap
@@ -13,6 +13,7 @@ exports[`should render correctly 1`] = `
"personalAccessToken": "",
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -35,6 +36,7 @@ exports[`should render correctly 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
index d86ec2f33cd..6dc5847e249 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
@@ -42,6 +42,7 @@ exports[`should render correctly 1`] = `
"url": "",
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -105,6 +106,7 @@ exports[`should render correctly 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap
index 60db1f5eb30..0ab619e3f37 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap
@@ -47,6 +47,7 @@ exports[`should render correctly: with branch support 1`] = `
"url": "",
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -97,6 +98,7 @@ exports[`should render correctly: with branch support 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap
index 1ab95326119..2ef3faa1a0d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap
@@ -42,6 +42,7 @@ exports[`should render correctly: with URL 1`] = `
"url": "",
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -71,6 +72,7 @@ exports[`should render correctly: with URL 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
@@ -131,6 +133,7 @@ exports[`should render correctly: with branch support 1`] = `
"url": "",
}
}
+ definitionStatus={Object {}}
definitions={
Array [
Object {
@@ -159,6 +162,7 @@ exports[`should render correctly: with branch support 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
@@ -219,6 +223,7 @@ exports[`should render correctly: with no definitions 1`] = `
"url": "",
}
}
+ definitionStatus={Object {}}
definitions={Array []}
features={
Array [
@@ -240,6 +245,7 @@ exports[`should render correctly: with no definitions 1`] = `
loadingAlmDefinitions={false}
loadingProjectCount={false}
multipleAlmEnabled={true}
+ onCheck={[MockFunction]}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts
new file mode 100644
index 00000000000..77af2d35efc
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/utils.ts
@@ -0,0 +1,22 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { AlmKeys } from '../../../../types/alm-settings';
+
+export const VALIDATED_ALMS = [AlmKeys.GitHub];
diff --git a/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts b/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
index c5bcc318785..b2bd99f0432 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
@@ -19,6 +19,7 @@
*/
import {
AlmKeys,
+ AlmSettingsBindingStatus,
AlmSettingsInstance,
AzureBindingDefinition,
BitbucketBindingDefinition,
@@ -129,3 +130,14 @@ export function mockProjectGitLabBindingResponse(
...overrides
};
}
+
+export function mockAlmSettingsBindingStatus(
+ overrides: Partial<AlmSettingsBindingStatus>
+): AlmSettingsBindingStatus {
+ return {
+ alert: false,
+ errorMessage: '',
+ validating: true,
+ ...overrides
+ };
+}
diff --git a/server/sonar-web/src/main/js/types/alm-settings.ts b/server/sonar-web/src/main/js/types/alm-settings.ts
index 887d227f881..8cc38476792 100644
--- a/server/sonar-web/src/main/js/types/alm-settings.ts
+++ b/server/sonar-web/src/main/js/types/alm-settings.ts
@@ -26,6 +26,7 @@ export const enum AlmKeys {
export interface AlmBindingDefinition {
key: string;
+ url?: string;
}
export interface AzureBindingDefinition extends AlmBindingDefinition {
@@ -108,3 +109,9 @@ export interface AlmSettingsBindingDefinitions {
[AlmKeys.GitHub]: GithubBindingDefinition[];
[AlmKeys.GitLab]: GitlabBindingDefinition[];
}
+
+export interface AlmSettingsBindingStatus {
+ alert: boolean;
+ errorMessage: string;
+ validating: boolean;
+}