]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12515 UI for azure PR decoration configuration
authorJeremy Davis <jeremy.davis@sonarsource.com>
Thu, 17 Oct 2019 15:58:46 +0000 (17:58 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 6 Nov 2019 09:04:26 +0000 (10:04 +0100)
56 files changed:
server/sonar-web/src/main/js/api/almSettings.ts
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmDefinitionFormField.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModal.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureFormModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTab.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTabRenderer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTable.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/DeleteModal.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubFormModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTab.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTabRenderer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTable.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTable.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTabs.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PullRequestDecoration.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabRenderer.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmDefinitionFormField-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationFormModal-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationFormModalRenderer-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureFormModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTab-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTabRenderer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTable-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubFormModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTab-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTabRenderer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTable-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PRDecorationTable-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PRDecorationTabs-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PullRequestDecoration-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmDefinitionFormField-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationFormModal-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationFormModalRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureFormModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTab-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTabRenderer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTable-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/DeleteModal-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubFormModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTab-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTabRenderer-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTable-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/PRDecorationTable-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/PRDecorationTabs-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/PullRequestDecoration-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabRenderer-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
server/sonar-web/src/main/js/apps/settings/utils.ts
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/types/alm-settings.d.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 655394cd658cca3348a54d72a81869c431950ce6..52cf1b61366e43b256fb748b98a27fe61ed034c7 100644 (file)
@@ -38,6 +38,14 @@ export function updateGithubConfiguration(data: T.GithubBindingDefinition & { ne
   return post('/api/alm_settings/update_github', data).catch(throwGlobalError);
 }
 
+export function createAzureConfiguration(data: T.AzureBindingDefinition) {
+  return post('/api/alm_settings/create_azure', data).catch(throwGlobalError);
+}
+
+export function updateAzureConfiguration(data: T.AzureBindingDefinition & { newKey: string }) {
+  return post('/api/alm_settings/update_azure', data).catch(throwGlobalError);
+}
+
 export function deleteConfiguration(key: string) {
   return post('/api/alm_settings/delete', { key }).catch(throwGlobalError);
 }
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmDefinitionFormField.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmDefinitionFormField.tsx
new file mode 100644 (file)
index 0000000..814930d
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+
+export interface AlmDefinitionFormFieldProps<B extends T.AlmSettingsBinding> {
+  autoFocus?: boolean;
+  formData: B;
+  help: boolean;
+  id: string;
+  isTextArea: boolean;
+  maxLength: number;
+  onFieldChange: (id: keyof B, value: string) => void;
+  propKey: keyof B;
+}
+
+export function AlmDefinitionFormField<B extends T.AlmSettingsBinding>(
+  props: AlmDefinitionFormFieldProps<B>
+) {
+  const { autoFocus, formData, help, id, isTextArea, maxLength, onFieldChange, propKey } = props;
+
+  return (
+    <div className="modal-field">
+      <label className="display-flex-center" htmlFor={id}>
+        {translate('settings.pr_decoration.form', id)}
+        <em className="mandatory spacer-right">*</em>
+        {help && <HelpTooltip overlay={translate('settings.pr_decoration.form', id, 'help')} />}
+      </label>
+      {isTextArea ? (
+        <textarea
+          className="settings-large-input"
+          id="privateKey"
+          maxLength={maxLength}
+          onChange={e => onFieldChange(propKey, e.currentTarget.value)}
+          required={true}
+          rows={5}
+          value={String(formData[propKey])}
+        />
+      ) : (
+        <input
+          autoFocus={autoFocus}
+          className="input-super-large"
+          id={id}
+          maxLength={maxLength}
+          name={id}
+          onChange={e => onFieldChange(propKey, e.currentTarget.value)}
+          size={50}
+          type="text"
+          value={String(formData[propKey])}
+        />
+      )}
+    </div>
+  );
+}
index 6eb11a4c0cb910229558c372ca86f97d5ed93a8b..fad0a66ae232160f381ecad2267abcda5ea28c28 100644 (file)
 import * as React from 'react';
 import AlmPRDecorationFormModalRenderer from './AlmPRDecorationFormModalRenderer';
 
-interface Props {
-  alm: string;
-  bindingDefinition: T.GithubBindingDefinition;
+interface ChildrenProps<AlmBindingDefinitionType> {
+  formData: AlmBindingDefinitionType;
+  onFieldChange: (fieldId: keyof AlmBindingDefinitionType, value: string) => void;
+}
+
+interface Props<B> {
+  children: (props: ChildrenProps<B>) => React.ReactNode;
+  bindingDefinition: B;
   onCancel: () => void;
-  onSubmit: (bindingDefinition: T.GithubBindingDefinition, originalKey: string) => void;
+  onSubmit: (data: B, originalKey: string) => void;
 }
 
-interface State {
-  formData: T.GithubBindingDefinition;
+interface State<AlmBindingDefinitionType> {
+  formData: AlmBindingDefinitionType;
 }
 
-export default class AlmPRDecorationFormModal extends React.PureComponent<Props, State> {
-  constructor(props: Props) {
+export default class AlmPRDecorationFormModal<
+  B extends T.AlmSettingsBinding
+> extends React.PureComponent<Props<B>, State<B>> {
+  constructor(props: Props<B>) {
     super(props);
 
     this.state = { formData: props.bindingDefinition };
   }
 
-  handleFieldChange = (fieldId: keyof T.GithubBindingDefinition, value: string) => {
+  handleFieldChange = (fieldId: keyof B, value: string) => {
     this.setState(({ formData }) => ({
       formData: {
         ...formData,
@@ -59,19 +66,20 @@ export default class AlmPRDecorationFormModal extends React.PureComponent<Props,
   };
 
   render() {
-    const { alm, bindingDefinition } = this.props;
+    const { children, bindingDefinition } = this.props;
     const { formData } = this.state;
 
     return (
       <AlmPRDecorationFormModalRenderer
-        alm={alm}
         canSubmit={this.canSubmit}
-        formData={formData}
         onCancel={this.props.onCancel}
-        onFieldChange={this.handleFieldChange}
         onSubmit={this.handleFormSubmit}
-        originalKey={bindingDefinition.key}
-      />
+        originalKey={bindingDefinition.key}>
+        {children({
+          formData,
+          onFieldChange: this.handleFieldChange
+        })}
+      </AlmPRDecorationFormModalRenderer>
     );
   }
 }
index 08993b527e04ab63b6707a09e9135ff25284ece2..cd575d4e50e52a07982105c1b8bc0cd08ec29f35 100644 (file)
  */
 import * as React from 'react';
 import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
-import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
 import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal';
 import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import { ALM_KEYS } from '../../utils';
 
 export interface AlmPRDecorationFormModalProps {
-  alm: string;
   canSubmit: () => boolean;
-  formData: T.GithubBindingDefinition;
+  children: React.ReactNode;
   onCancel: () => void;
   onSubmit: () => void;
-  onFieldChange: (id: string, value: string) => void;
   originalKey: string;
 }
 
-function renderField(params: {
-  autoFocus?: boolean;
-  formData: T.GithubBindingDefinition;
-  help: boolean;
-  id: string;
-  isTextArea: boolean;
-  maxLength: number;
-  onFieldChange: (id: string, value: string) => void;
-  propKey: keyof T.GithubBindingDefinition;
-}) {
-  const { autoFocus, formData, help, id, isTextArea, maxLength, onFieldChange, propKey } = params;
-  return (
-    <div className="modal-field">
-      <label className="display-flex-center" htmlFor={id}>
-        {translate('settings.pr_decoration.form', id)}
-        <em className="mandatory spacer-right">*</em>
-        {help && <HelpTooltip overlay={translate('settings.pr_decoration.form', id, 'help')} />}
-      </label>
-      {isTextArea ? (
-        <textarea
-          className="settings-large-input"
-          id="privateKey"
-          maxLength={maxLength}
-          onChange={e => onFieldChange(propKey, e.currentTarget.value)}
-          required={true}
-          rows={5}
-          value={formData[propKey]}
-        />
-      ) : (
-        <input
-          autoFocus={autoFocus}
-          className="input-super-large"
-          id={id}
-          maxLength={maxLength}
-          name={id}
-          onChange={e => onFieldChange(propKey, e.currentTarget.value)}
-          size={50}
-          type="text"
-          value={formData[propKey]}
-        />
-      )}
-    </div>
-  );
-}
-
 export default function AlmPRDecorationFormModalRenderer(props: AlmPRDecorationFormModalProps) {
-  const { alm, formData, onFieldChange, originalKey } = props;
+  const { children, originalKey } = props;
   const header = translate('settings.pr_decoration.form.header', originalKey ? 'edit' : 'create');
 
   return (
@@ -92,46 +43,7 @@ export default function AlmPRDecorationFormModalRenderer(props: AlmPRDecorationF
             <h2>{header}</h2>
           </div>
 
-          <div className="modal-body modal-container">
-            {renderField({
-              autoFocus: true,
-              id: 'name',
-              formData,
-              propKey: 'key',
-              maxLength: 40,
-              onFieldChange,
-              help: true,
-              isTextArea: false
-            })}
-            {renderField({
-              id: `url.${alm}`,
-              formData,
-              propKey: 'url',
-              maxLength: 2000,
-              onFieldChange,
-              help: false,
-              isTextArea: false
-            })}
-            {alm === ALM_KEYS.GITHUB &&
-              renderField({
-                id: 'app_id',
-                formData,
-                propKey: 'appId',
-                maxLength: 80,
-                onFieldChange,
-                help: false,
-                isTextArea: false
-              })}
-            {renderField({
-              id: 'private_key',
-              formData,
-              propKey: 'privateKey',
-              maxLength: 2000,
-              onFieldChange,
-              help: false,
-              isTextArea: true
-            })}
-          </div>
+          <div className="modal-body modal-container">{children}</div>
 
           <div className="modal-foot">
             <DeferredSpinner className="spacer-right" loading={submitting} />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureFormModal.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureFormModal.tsx
new file mode 100644 (file)
index 0000000..187dcc5
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { AlmDefinitionFormField } from './AlmDefinitionFormField';
+
+export interface AzureFormModalProps {
+  formData: T.AzureBindingDefinition;
+  onFieldChange: (fieldId: keyof T.AzureBindingDefinition, value: string) => void;
+}
+
+export default function AzureFormModal(props: AzureFormModalProps) {
+  const { formData, onFieldChange } = props;
+
+  return (
+    <>
+      <AlmDefinitionFormField
+        autoFocus={true}
+        formData={formData}
+        help={true}
+        id="name"
+        isTextArea={false}
+        maxLength={40}
+        onFieldChange={onFieldChange}
+        propKey="key"
+      />
+      <AlmDefinitionFormField
+        formData={formData}
+        help={true}
+        id="personal_access_token"
+        isTextArea={true}
+        maxLength={2000}
+        onFieldChange={onFieldChange}
+        propKey="personalAccessToken"
+      />
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTab.tsx
new file mode 100644 (file)
index 0000000..a9c8aec
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { createAzureConfiguration, updateAzureConfiguration } from '../../../../api/almSettings';
+import AzureTabRenderer from './AzureTabRenderer';
+
+interface Props {
+  definitions: T.AzureBindingDefinition[];
+  loading: boolean;
+  onDelete: (definitionKey: string) => void;
+  onUpdateDefinitions: () => void;
+}
+
+interface State {
+  editedDefinition?: T.AzureBindingDefinition;
+  projectCount?: number;
+}
+
+export default class AzureTab extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {};
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancel = () => {
+    this.setState({
+      editedDefinition: undefined
+    });
+  };
+
+  handleCreate = () => {
+    this.setState({ editedDefinition: { key: '', personalAccessToken: '' } });
+  };
+
+  handleEdit = (config: T.AzureBindingDefinition) => {
+    this.setState({ editedDefinition: config });
+  };
+
+  handleSubmit = (config: T.AzureBindingDefinition, originalKey: string) => {
+    const call = originalKey
+      ? updateAzureConfiguration({ newKey: config.key, ...config, key: originalKey })
+      : createAzureConfiguration(config);
+    return call.then(this.props.onUpdateDefinitions).then(() => {
+      if (this.mounted) {
+        this.setState({ editedDefinition: undefined });
+      }
+    });
+  };
+
+  render() {
+    const { definitions, loading } = this.props;
+    const { editedDefinition } = this.state;
+    return (
+      <AzureTabRenderer
+        definitions={definitions}
+        editedDefinition={editedDefinition}
+        loading={loading}
+        onCancel={this.handleCancel}
+        onCreate={this.handleCreate}
+        onDelete={this.props.onDelete}
+        onEdit={this.handleEdit}
+        onSubmit={this.handleSubmit}
+      />
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTabRenderer.tsx
new file mode 100644 (file)
index 0000000..b8d1d9e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ALM_KEYS } from '../../utils';
+import AlmPRDecorationFormModal from './AlmPRDecorationFormModal';
+import AzureFormModal from './AzureFormModal';
+import AzureTable from './AzureTable';
+import TabHeader from './TabHeader';
+
+export interface AzureTabRendererProps {
+  editedDefinition?: T.AzureBindingDefinition;
+  definitions: T.AzureBindingDefinition[];
+  loading: boolean;
+  onCancel: () => void;
+  onCreate: () => void;
+  onDelete: (definitionKey: string) => void;
+  onEdit: (config: T.AzureBindingDefinition) => void;
+  onSubmit: (config: T.AzureBindingDefinition, originalKey: string) => void;
+}
+
+export default function AzureTabRenderer(props: AzureTabRendererProps) {
+  const { definitions, editedDefinition, loading } = props;
+  return (
+    <>
+      <TabHeader alm={ALM_KEYS.AZURE} onCreate={props.onCreate} />
+
+      <AzureTable
+        definitions={definitions}
+        loading={loading}
+        onDelete={props.onDelete}
+        onEdit={props.onEdit}
+      />
+
+      {editedDefinition && (
+        <AlmPRDecorationFormModal
+          bindingDefinition={editedDefinition}
+          onCancel={props.onCancel}
+          onSubmit={props.onSubmit}>
+          {childProps => <AzureFormModal {...childProps} />}
+        </AlmPRDecorationFormModal>
+      )}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTable.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTable.tsx
new file mode 100644 (file)
index 0000000..93b224e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ButtonIcon, DeleteButton } from 'sonar-ui-common/components/controls/buttons';
+import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
+import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+
+export interface AzureTableProps {
+  definitions: T.AzureBindingDefinition[];
+  loading: boolean;
+  onDelete: (definitionKey: string) => void;
+  onEdit: (config: T.AzureBindingDefinition) => void;
+}
+
+export default function AzureTable(props: AzureTableProps) {
+  const { definitions, loading } = props;
+
+  if (loading) {
+    return <DeferredSpinner />;
+  }
+
+  return (
+    <table className="data zebra spacer-bottom">
+      <thead>
+        <tr>
+          <th>{translate('settings.pr_decoration.table.column.name')}</th>
+          <th className="thin">{translate('settings.pr_decoration.table.column.edit')}</th>
+          <th className="thin">{translate('settings.pr_decoration.table.column.delete')}</th>
+        </tr>
+      </thead>
+      <tbody>
+        {definitions.length < 1 ? (
+          <tr>
+            <td colSpan={3}>{translate('settings.pr_decoration.table.empty.azure')}</td>
+          </tr>
+        ) : (
+          definitions.map(definition => (
+            <tr key={definition.key}>
+              <td>{definition.key}</td>
+              <td>
+                <ButtonIcon onClick={() => props.onEdit(definition)}>
+                  <EditIcon />
+                </ButtonIcon>
+              </td>
+              <td>
+                <DeleteButton onClick={() => props.onDelete(definition.key)} />
+              </td>
+            </tr>
+          ))
+        )}
+      </tbody>
+    </table>
+  );
+}
index 4741fa0194ef6812965f0ba091f241b67f120cb3..03a4c248a8f7122342cb215c6be2b2ffc357d7be 100644 (file)
@@ -45,6 +45,7 @@ export default function DeleteModal({ id, onDelete, onCancel, projectCount }: De
       confirmButtonText={translate('delete')}
       confirmData={id}
       header={translate('settings.pr_decoration.delete.header')}
+      isDestructive={true}
       onClose={onCancel}
       onConfirm={onDelete}>
       <>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubFormModal.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubFormModal.tsx
new file mode 100644 (file)
index 0000000..0386b9a
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { AlmDefinitionFormField } from './AlmDefinitionFormField';
+
+export interface GithubFormModalProps {
+  formData: T.GithubBindingDefinition;
+  onFieldChange: (fieldId: keyof T.GithubBindingDefinition, value: string) => void;
+}
+
+export default function GithubFormModal(props: GithubFormModalProps) {
+  const { formData, onFieldChange } = props;
+
+  return (
+    <>
+      <AlmDefinitionFormField
+        autoFocus={true}
+        formData={formData}
+        help={true}
+        id="name"
+        isTextArea={false}
+        maxLength={40}
+        onFieldChange={onFieldChange}
+        propKey="key"
+      />
+      <AlmDefinitionFormField
+        formData={formData}
+        help={false}
+        id="url.github"
+        isTextArea={false}
+        maxLength={2000}
+        onFieldChange={onFieldChange}
+        propKey="url"
+      />
+      <AlmDefinitionFormField
+        formData={formData}
+        help={false}
+        id="app_id"
+        isTextArea={false}
+        maxLength={80}
+        onFieldChange={onFieldChange}
+        propKey="appId"
+      />
+      <AlmDefinitionFormField
+        formData={formData}
+        help={false}
+        id="private_key"
+        isTextArea={true}
+        maxLength={2000}
+        onFieldChange={onFieldChange}
+        propKey="privateKey"
+      />
+    </>
+  );
+}
index 0057014cb91baa1c994f3755fd220aef83184a63..f68e5272ecfaa83d752fa3a169ad7f7520969b1e 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import {
-  countBindedProjects,
-  createGithubConfiguration,
-  deleteConfiguration,
-  updateGithubConfiguration
-} from '../../../../api/almSettings';
-import { ALM_KEYS } from '../../utils';
-import TabRenderer from './TabRenderer';
+import { createGithubConfiguration, updateGithubConfiguration } from '../../../../api/almSettings';
+import GithubTabRenderer from './GithubTabRenderer';
 
 interface Props {
   definitions: T.GithubBindingDefinition[];
+  loading: boolean;
+  onDelete: (definitionKey: string) => void;
   onUpdateDefinitions: () => void;
 }
 
 interface State {
-  definitionInEdition?: T.GithubBindingDefinition;
-  definitionKeyForDeletion?: string;
+  editedDefinition?: T.GithubBindingDefinition;
   projectCount?: number;
 }
 
@@ -52,67 +47,44 @@ export default class GithubTab extends React.PureComponent<Props, State> {
 
   handleCancel = () => {
     this.setState({
-      definitionKeyForDeletion: undefined,
-      definitionInEdition: undefined,
-      projectCount: undefined
+      editedDefinition: undefined
     });
   };
 
-  deleteConfiguration = (id: string) => {
-    return deleteConfiguration(id)
-      .then(this.props.onUpdateDefinitions)
-      .then(() => {
-        if (this.mounted) {
-          this.setState({ definitionKeyForDeletion: undefined });
-        }
-      });
-  };
-
   handleCreate = () => {
-    this.setState({ definitionInEdition: { key: '', appId: '', url: '', privateKey: '' } });
-  };
-
-  handleDelete = (config: T.GithubBindingDefinition) => {
-    this.setState({ definitionKeyForDeletion: config.key });
-
-    return countBindedProjects(config.key).then(projectCount => {
-      if (this.mounted) {
-        this.setState({ projectCount });
-      }
-    });
+    this.setState({ editedDefinition: { key: '', appId: '', url: '', privateKey: '' } });
   };
 
   handleEdit = (config: T.GithubBindingDefinition) => {
-    this.setState({ definitionInEdition: config });
+    this.setState({ editedDefinition: config });
   };
 
   handleSubmit = (config: T.GithubBindingDefinition, originalKey: string) => {
     const call = originalKey
       ? updateGithubConfiguration({ newKey: config.key, ...config, key: originalKey })
       : createGithubConfiguration(config);
-    return call.then(this.props.onUpdateDefinitions).then(() => {
-      if (this.mounted) {
-        this.setState({ definitionInEdition: undefined });
-      }
-    });
+    return call
+      .then(() => {
+        if (this.mounted) {
+          this.setState({ editedDefinition: undefined });
+        }
+      })
+      .then(this.props.onUpdateDefinitions);
   };
 
   render() {
-    const { definitions } = this.props;
-    const { definitionKeyForDeletion, definitionInEdition, projectCount } = this.state;
+    const { definitions, loading } = this.props;
+    const { editedDefinition } = this.state;
     return (
-      <TabRenderer
-        alm={ALM_KEYS.GITHUB}
-        definitionInEdition={definitionInEdition}
-        definitionKeyForDeletion={definitionKeyForDeletion}
+      <GithubTabRenderer
         definitions={definitions}
+        editedDefinition={editedDefinition}
+        loading={loading}
         onCancel={this.handleCancel}
-        onConfirmDelete={this.deleteConfiguration}
         onCreate={this.handleCreate}
-        onDelete={this.handleDelete}
+        onDelete={this.props.onDelete}
         onEdit={this.handleEdit}
         onSubmit={this.handleSubmit}
-        projectCount={projectCount}
       />
     );
   }
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTabRenderer.tsx
new file mode 100644 (file)
index 0000000..e7161e2
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ALM_KEYS } from '../../utils';
+import AlmPRDecorationFormModal from './AlmPRDecorationFormModal';
+import GithubFormModal from './GithubFormModal';
+import GithubTable from './GithubTable';
+import TabHeader from './TabHeader';
+
+export interface GithubTabRendererProps {
+  editedDefinition?: T.GithubBindingDefinition;
+  definitions: T.GithubBindingDefinition[];
+  loading: boolean;
+  onCancel: () => void;
+  onCreate: () => void;
+  onDelete: (definitionKey: string) => void;
+  onEdit: (config: T.GithubBindingDefinition) => void;
+  onSubmit: (config: T.GithubBindingDefinition, originalKey: string) => void;
+}
+
+export default function GithubTabRenderer(props: GithubTabRendererProps) {
+  const { definitions, editedDefinition, loading } = props;
+  return (
+    <>
+      <TabHeader alm={ALM_KEYS.GITHUB} onCreate={props.onCreate} />
+
+      <GithubTable
+        definitions={definitions}
+        loading={loading}
+        onDelete={props.onDelete}
+        onEdit={props.onEdit}
+      />
+
+      {editedDefinition && (
+        <AlmPRDecorationFormModal
+          bindingDefinition={editedDefinition}
+          onCancel={props.onCancel}
+          onSubmit={props.onSubmit}>
+          {childProps => <GithubFormModal {...childProps} />}
+        </AlmPRDecorationFormModal>
+      )}
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTable.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTable.tsx
new file mode 100644 (file)
index 0000000..5cb6e5f
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ButtonIcon, DeleteButton } from 'sonar-ui-common/components/controls/buttons';
+import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
+import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+
+export interface GithubTableProps {
+  definitions: T.GithubBindingDefinition[];
+  loading: boolean;
+  onDelete: (definitionKey: string) => void;
+  onEdit: (config: T.GithubBindingDefinition) => void;
+}
+
+export default function GithubTable(props: GithubTableProps) {
+  const { definitions, loading } = props;
+
+  if (loading) {
+    return <DeferredSpinner />;
+  }
+
+  return (
+    <table className="data zebra spacer-bottom">
+      <thead>
+        <tr>
+          <th>{translate('settings.pr_decoration.table.column.name')}</th>
+          <th>{translate(`settings.pr_decoration.table.column.github.url`)}</th>
+          <th>{translate('settings.pr_decoration.table.column.app_id')}</th>
+          <th className="thin">{translate('settings.pr_decoration.table.column.edit')}</th>
+          <th className="thin">{translate('settings.pr_decoration.table.column.delete')}</th>
+        </tr>
+      </thead>
+      <tbody>
+        {definitions.map(definition => (
+          <tr key={definition.key}>
+            <td>{definition.key}</td>
+            <td>{definition.url}</td>
+            <td>{definition.appId}</td>
+            <td>
+              <ButtonIcon onClick={() => props.onEdit(definition)}>
+                <EditIcon />
+              </ButtonIcon>
+            </td>
+            <td>
+              <DeleteButton onClick={() => props.onDelete(definition.key)} />
+            </td>
+          </tr>
+        ))}
+      </tbody>
+    </table>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTable.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTable.tsx
deleted file mode 100644 (file)
index b86827f..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { ButtonIcon } from 'sonar-ui-common/components/controls/buttons';
-import DeleteIcon from 'sonar-ui-common/components/icons/DeleteIcon';
-import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-import { ALM_KEYS } from '../../utils';
-
-export interface PRDecorationTableProps {
-  alm: ALM_KEYS;
-  definitions: T.GithubBindingDefinition[];
-  onDelete: (config: T.GithubBindingDefinition) => void;
-  onEdit: (config: T.GithubBindingDefinition) => void;
-}
-
-export default function PRDecorationTable(props: PRDecorationTableProps) {
-  const { alm, definitions } = props;
-
-  return (
-    <>
-      <table className="data zebra spacer-bottom">
-        <thead>
-          <tr>
-            <th>{translate('settings.pr_decoration.table.column.name')}</th>
-            <th>{translate(`settings.pr_decoration.table.column.${alm}.url`)}</th>
-            <th>{translate('settings.pr_decoration.table.column.app_id')}</th>
-            <th className="thin">{translate('settings.pr_decoration.table.column.edit')}</th>
-            <th className="thin">{translate('settings.pr_decoration.table.column.delete')}</th>
-          </tr>
-        </thead>
-        <tbody>
-          {definitions.map(definition => (
-            <tr key={definition.key}>
-              <td>{definition.key}</td>
-              <td>{definition.url}</td>
-              <td>{definition.appId}</td>
-              <td>
-                <ButtonIcon onClick={() => props.onEdit(definition)}>
-                  <EditIcon />
-                </ButtonIcon>
-              </td>
-              <td>
-                <ButtonIcon onClick={() => props.onDelete(definition)}>
-                  <DeleteIcon />
-                </ButtonIcon>
-              </td>
-            </tr>
-          ))}
-        </tbody>
-      </table>
-    </>
-  );
-}
index fba5a399de9951e0e953311e6002c2ff1c8a87e2..a10de20c8fe791db72c1450e1009c1e40de244bd 100644 (file)
  */
 import * as React from 'react';
 import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
-import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import { almName, ALM_KEYS } from '../../utils';
+import AzureTab from './AzureTab';
+import DeleteModal from './DeleteModal';
 import GithubTab from './GithubTab';
 
 export interface PRDecorationTabsProps {
   currentAlm: ALM_KEYS;
+  definitionKeyForDeletion?: string;
   definitions: T.AlmSettingsBindingDefinitions;
   loading: boolean;
+  onCancel: () => void;
+  onConfirmDelete: (definitionKey: string) => void;
+  onDelete: (definitionKey: string) => void;
   onSelectAlm: (alm: ALM_KEYS) => void;
   onUpdateDefinitions: () => void;
+  projectCount?: number;
 }
 
 export default function PRDecorationTabs(props: PRDecorationTabsProps) {
-  const { definitions, currentAlm, loading } = props;
-
-  if (loading) {
-    return <DeferredSpinner />;
-  }
+  const { definitionKeyForDeletion, definitions, currentAlm, loading, projectCount } = props;
 
   return (
     <>
@@ -55,16 +57,41 @@ export default function PRDecorationTabs(props: PRDecorationTabsProps) {
           {
             key: ALM_KEYS.GITHUB,
             label: almName[ALM_KEYS.GITHUB]
+          },
+          {
+            key: ALM_KEYS.AZURE,
+            label: almName[ALM_KEYS.AZURE]
           }
         ]}
       />
 
       <div className="boxed-group boxed-group-inner">
-        <GithubTab
-          definitions={definitions.github}
-          onUpdateDefinitions={props.onUpdateDefinitions}
-        />
+        {currentAlm === ALM_KEYS.AZURE && (
+          <AzureTab
+            definitions={definitions.azure}
+            loading={loading}
+            onDelete={props.onDelete}
+            onUpdateDefinitions={props.onUpdateDefinitions}
+          />
+        )}
+        {currentAlm === ALM_KEYS.GITHUB && (
+          <GithubTab
+            definitions={definitions.github}
+            loading={loading}
+            onDelete={props.onDelete}
+            onUpdateDefinitions={props.onUpdateDefinitions}
+          />
+        )}
       </div>
+
+      {definitionKeyForDeletion && (
+        <DeleteModal
+          id={definitionKeyForDeletion}
+          onCancel={props.onCancel}
+          onDelete={props.onConfirmDelete}
+          projectCount={projectCount}
+        />
+      )}
     </>
   );
 }
index fd60606b81ee639a553d6b8f6a0a3b8b5ad0a4db..85f81567b5c2c27bb3930b196dc4ef5f0d2bb821 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { getAlmDefinitions } from '../../../../api/almSettings';
+import {
+  countBindedProjects,
+  deleteConfiguration,
+  getAlmDefinitions
+} from '../../../../api/almSettings';
 import { ALM_KEYS } from '../../utils';
 import PRDecorationTabs from './PRDecorationTabs';
 
 interface State {
   currentAlm: ALM_KEYS;
+  definitionKeyForDeletion?: string;
   definitions: T.AlmSettingsBindingDefinitions;
   loading: boolean;
+  projectCount?: number;
 }
 
 export default class PullRequestDecoration extends React.PureComponent<{}, State> {
@@ -33,6 +39,7 @@ export default class PullRequestDecoration extends React.PureComponent<{}, State
   state: State = {
     currentAlm: ALM_KEYS.GITHUB,
     definitions: {
+      [ALM_KEYS.AZURE]: [],
       [ALM_KEYS.GITHUB]: []
     },
     loading: true
@@ -47,7 +54,18 @@ export default class PullRequestDecoration extends React.PureComponent<{}, State
     this.mounted = false;
   }
 
+  deleteConfiguration = (definitionKey: string) => {
+    return deleteConfiguration(definitionKey)
+      .then(() => {
+        if (this.mounted) {
+          this.setState({ definitionKeyForDeletion: undefined, projectCount: undefined });
+        }
+      })
+      .then(this.fetchPullRequestDecorationSetting);
+  };
+
   fetchPullRequestDecorationSetting = () => {
+    this.setState({ loading: true });
     return getAlmDefinitions()
       .then(definitions => {
         if (this.mounted) {
@@ -68,9 +86,27 @@ export default class PullRequestDecoration extends React.PureComponent<{}, State
     this.setState({ currentAlm });
   };
 
+  handleCancel = () => {
+    this.setState({ definitionKeyForDeletion: undefined, projectCount: undefined });
+  };
+
+  handleDelete = (definitionKey: string) => {
+    return countBindedProjects(definitionKey).then(projectCount => {
+      if (this.mounted) {
+        this.setState({
+          definitionKeyForDeletion: definitionKey,
+          projectCount
+        });
+      }
+    });
+  };
+
   render() {
     return (
       <PRDecorationTabs
+        onCancel={this.handleCancel}
+        onConfirmDelete={this.deleteConfiguration}
+        onDelete={this.handleDelete}
         onSelectAlm={this.handleSelectAlm}
         onUpdateDefinitions={this.fetchPullRequestDecorationSetting}
         {...this.state}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabHeader.tsx
new file mode 100644 (file)
index 0000000..4fbd9d7
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router';
+import { Button } from 'sonar-ui-common/components/controls/buttons';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import { ALM_KEYS } from '../../utils';
+
+export interface TabHeaderProps {
+  alm: ALM_KEYS;
+  onCreate: () => void;
+}
+
+export default function TabHeader(props: TabHeaderProps) {
+  const { alm } = props;
+  return (
+    <>
+      <Alert className="spacer-top huge-spacer-bottom" variant="info">
+        <FormattedMessage
+          defaultMessage={translate(`settings.pr_decoration.${alm}.info`)}
+          id={`settings.pr_decoration.${alm}.info`}
+          values={{
+            link: (
+              <Link to="/documentation/analysis/pull-request/#pr-decoration">
+                {translate('learn_more')}
+              </Link>
+            )
+          }}
+        />
+      </Alert>
+
+      <div className="big-spacer-bottom display-flex-space-between">
+        <h4 className="display-inline">{translate('settings.pr_decoration.table.title')}</h4>
+        <Button onClick={props.onCreate}>{translate('settings.pr_decoration.table.create')}</Button>
+      </div>
+    </>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/TabRenderer.tsx
deleted file mode 100644 (file)
index 2eed336..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
-import { Button } from 'sonar-ui-common/components/controls/buttons';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-import { ALM_KEYS } from '../../utils';
-import AlmPRDecorationFormModal from './AlmPRDecorationFormModal';
-import DeleteModal from './DeleteModal';
-import PRDecorationTable from './PRDecorationTable';
-
-export interface TabRendererProps {
-  alm: ALM_KEYS;
-  definitionInEdition?: T.GithubBindingDefinition;
-  definitionKeyForDeletion?: string;
-  definitions: T.GithubBindingDefinition[];
-  onCancel: () => void;
-  onConfirmDelete: (id: string) => void;
-  onCreate: () => void;
-  onDelete: (config: T.GithubBindingDefinition) => void;
-  onEdit: (config: T.GithubBindingDefinition) => void;
-  onSubmit: (config: T.GithubBindingDefinition, originalKey: string) => void;
-  projectCount?: number;
-}
-
-export default function TabRenderer(props: TabRendererProps) {
-  const { alm, definitions, definitionKeyForDeletion, definitionInEdition, projectCount } = props;
-  return (
-    <>
-      <Alert className="spacer-top huge-spacer-bottom" variant="info">
-        <FormattedMessage
-          defaultMessage={translate(`settings.pr_decoration.${alm}.info`)}
-          id={`settings.pr_decoration.${alm}.info`}
-          values={{
-            link: (
-              <Link to="/documentation/analysis/pull-request/#pr-decoration">
-                {translate('learn_more')}
-              </Link>
-            )
-          }}
-        />
-      </Alert>
-
-      <div className="big-spacer-bottom display-flex-space-between">
-        <h4 className="display-inline">{translate('settings.pr_decoration.table.title')}</h4>
-        <Button onClick={props.onCreate}>{translate('settings.pr_decoration.table.create')}</Button>
-      </div>
-
-      <PRDecorationTable
-        alm={alm}
-        definitions={definitions}
-        onDelete={props.onDelete}
-        onEdit={props.onEdit}
-      />
-      {definitionKeyForDeletion && (
-        <DeleteModal
-          id={definitionKeyForDeletion}
-          onCancel={props.onCancel}
-          onDelete={props.onConfirmDelete}
-          projectCount={projectCount}
-        />
-      )}
-
-      {definitionInEdition && (
-        <AlmPRDecorationFormModal
-          alm={ALM_KEYS.GITHUB}
-          bindingDefinition={definitionInEdition}
-          onCancel={props.onCancel}
-          onSubmit={props.onSubmit}
-        />
-      )}
-    </>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmDefinitionFormField-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmDefinitionFormField-test.tsx
new file mode 100644 (file)
index 0000000..0d606dc
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { AlmDefinitionFormField, AlmDefinitionFormFieldProps } from '../AlmDefinitionFormField';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ help: true })).toMatchSnapshot();
+  expect(shallowRender({ isTextArea: true })).toMatchSnapshot();
+});
+
+it('should call onFieldChange', () => {
+  const onInputChange = jest.fn();
+  shallowRender({ onFieldChange: onInputChange })
+    .find('input')
+    .simulate('change', { currentTarget: { value: '' } });
+  expect(onInputChange).toBeCalled();
+
+  const onTextAreaChange = jest.fn();
+  shallowRender({ isTextArea: true, onFieldChange: onTextAreaChange })
+    .find('textarea')
+    .simulate('change', { currentTarget: { value: '' } });
+  expect(onTextAreaChange).toBeCalled();
+});
+
+function shallowRender(props: Partial<AlmDefinitionFormFieldProps<T.AlmSettingsBinding>> = {}) {
+  return shallow(
+    <AlmDefinitionFormField
+      formData={{ key: 'key' }}
+      help={false}
+      id="key"
+      isTextArea={false}
+      maxLength={40}
+      onFieldChange={jest.fn()}
+      propKey="key"
+      {...props}
+    />
+  );
+}
index 7c091933152017b36debc202226f133a7a577f84..d0fe3a4162a257f3bb96009d59532d2b0a97cffc 100644 (file)
@@ -21,7 +21,6 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
 import { mockGithubDefinition } from '../../../../../helpers/testMocks';
-import { ALM_KEYS } from '../../../utils';
 import AlmPRDecorationFormModal from '../AlmPRDecorationFormModal';
 
 it('should render correctly', () => {
@@ -75,14 +74,16 @@ it('should (dis)allow submit by validating its state', async () => {
   expect(wrapper.instance().canSubmit()).toBe(true);
 });
 
-function shallowRender(props: Partial<AlmPRDecorationFormModal['props']> = {}) {
-  return shallow<AlmPRDecorationFormModal>(
+function shallowRender(
+  props: Partial<AlmPRDecorationFormModal<T.GithubBindingDefinition>['props']> = {}
+) {
+  return shallow<AlmPRDecorationFormModal<T.GithubBindingDefinition>>(
     <AlmPRDecorationFormModal
-      alm={ALM_KEYS.GITHUB}
       bindingDefinition={{ appId: '', key: '', privateKey: '', url: '' }}
       onCancel={jest.fn()}
       onSubmit={jest.fn()}
-      {...props}
-    />
+      {...props}>
+      {() => null}
+    </AlmPRDecorationFormModal>
   );
 }
index 1b457d46c0dbad1cde021764821e649e044b19b0..2372fac9458dff43b8c7b17abc26d535f8c7f9a8 100644 (file)
@@ -19,8 +19,6 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockGithubDefinition } from '../../../../../helpers/testMocks';
-import { ALM_KEYS } from '../../../utils';
 import AlmPRDecorationFormModalRenderer, {
   AlmPRDecorationFormModalProps
 } from '../AlmPRDecorationFormModalRenderer';
@@ -32,14 +30,12 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<AlmPRDecorationFormModalProps> = {}) {
   return shallow(
     <AlmPRDecorationFormModalRenderer
-      alm={ALM_KEYS.GITHUB}
       canSubmit={jest.fn()}
-      formData={mockGithubDefinition()}
       onCancel={jest.fn()}
-      onFieldChange={jest.fn()}
       onSubmit={jest.fn()}
       originalKey=""
-      {...props}
-    />
+      {...props}>
+      {() => null}
+    </AlmPRDecorationFormModalRenderer>
   );
 }
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureFormModal-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureFormModal-test.tsx
new file mode 100644 (file)
index 0000000..37078fe
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockAzureDefinition } from '../../../../../helpers/testMocks';
+import AzureFormModal, { AzureFormModalProps } from '../AzureFormModal';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ formData: mockAzureDefinition() })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<AzureFormModalProps> = {}) {
+  return shallow(
+    <AzureFormModal
+      formData={{ key: '', personalAccessToken: '' }}
+      onFieldChange={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTab-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTab-test.tsx
new file mode 100644 (file)
index 0000000..e98737b
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { createAzureConfiguration, updateAzureConfiguration } from '../../../../../api/almSettings';
+import { mockAzureDefinition } from '../../../../../helpers/testMocks';
+import AzureTab from '../AzureTab';
+
+jest.mock('../../../../../api/almSettings', () => ({
+  countBindedProjects: jest.fn().mockResolvedValue(2),
+  createAzureConfiguration: jest.fn().mockResolvedValue({}),
+  deleteConfiguration: jest.fn().mockResolvedValue({}),
+  updateAzureConfiguration: jest.fn().mockResolvedValue({})
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should handle cancel', async () => {
+  const wrapper = shallowRender();
+
+  wrapper.setState({
+    editedDefinition: mockAzureDefinition()
+  });
+
+  wrapper.instance().handleCancel();
+
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.state().editedDefinition).toBeUndefined();
+});
+
+it('should create config', async () => {
+  const onUpdateDefinitions = jest.fn();
+  const config = mockAzureDefinition();
+  const wrapper = shallowRender({ onUpdateDefinitions });
+  wrapper.setState({ editedDefinition: config });
+
+  await wrapper.instance().handleSubmit(config, '');
+
+  expect(createAzureConfiguration).toBeCalledWith(config);
+  expect(onUpdateDefinitions).toBeCalled();
+  expect(wrapper.state().editedDefinition).toBeUndefined();
+});
+
+it('should update config', async () => {
+  const onUpdateDefinitions = jest.fn();
+  const config = mockAzureDefinition();
+  const wrapper = shallowRender({ onUpdateDefinitions });
+  wrapper.setState({ editedDefinition: config });
+
+  await wrapper.instance().handleSubmit(config, 'originalKey');
+
+  expect(updateAzureConfiguration).toBeCalledWith({
+    newKey: 'key',
+    ...config,
+    key: 'originalKey'
+  });
+  expect(onUpdateDefinitions).toBeCalled();
+  expect(wrapper.state().editedDefinition).toBeUndefined();
+});
+
+function shallowRender(props: Partial<AzureTab['props']> = {}) {
+  return shallow<AzureTab>(
+    <AzureTab
+      definitions={[]}
+      loading={false}
+      onDelete={jest.fn()}
+      onUpdateDefinitions={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTabRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTabRenderer-test.tsx
new file mode 100644 (file)
index 0000000..ea3bfed
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockAzureDefinition } from '../../../../../helpers/testMocks';
+import AzureTabRenderer, { AzureTabRendererProps } from '../AzureTabRenderer';
+
+it('should render correctly', () => {
+  expect(shallowRender({ loading: true })).toMatchSnapshot();
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ editedDefinition: mockAzureDefinition() })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<AzureTabRendererProps> = {}) {
+  return shallow(
+    <AzureTabRenderer
+      definitions={[]}
+      loading={false}
+      onCancel={jest.fn()}
+      onCreate={jest.fn()}
+      onDelete={jest.fn()}
+      onEdit={jest.fn()}
+      onSubmit={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTable-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTable-test.tsx
new file mode 100644 (file)
index 0000000..e6e7fcb
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockAzureDefinition } from '../../../../../helpers/testMocks';
+import AzureTable, { AzureTableProps } from '../AzureTable';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ definitions: [mockAzureDefinition()] })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<AzureTableProps> = {}) {
+  return shallow(
+    <AzureTable
+      definitions={[]}
+      loading={false}
+      onDelete={jest.fn()}
+      onEdit={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubFormModal-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubFormModal-test.tsx
new file mode 100644 (file)
index 0000000..cd9a383
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockGithubDefinition } from '../../../../../helpers/testMocks';
+import GithubFormModal, { GithubFormModalProps } from '../GithubFormModal';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ formData: mockGithubDefinition() })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<GithubFormModalProps> = {}) {
+  return shallow(
+    <GithubFormModal
+      formData={{ key: '', appId: '', privateKey: '', url: '' }}
+      onFieldChange={jest.fn()}
+      {...props}
+    />
+  );
+}
index c2c9b969be04a5058d0f69548a2e13f559d17062..dac085c21ba20c8e7bdbac591ede0f4dbc8a48e8 100644 (file)
@@ -22,7 +22,6 @@ import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
 import {
   createGithubConfiguration,
-  deleteConfiguration,
   updateGithubConfiguration
 } from '../../../../../api/almSettings';
 import { mockGithubDefinition } from '../../../../../helpers/testMocks';
@@ -47,81 +46,54 @@ it('should handle cancel', async () => {
   const wrapper = shallowRender();
 
   wrapper.setState({
-    definitionKeyForDeletion: '12321',
-    definitionInEdition: mockGithubDefinition()
+    editedDefinition: mockGithubDefinition()
   });
 
   wrapper.instance().handleCancel();
 
   await waitAndUpdate(wrapper);
 
-  expect(wrapper.state().definitionKeyForDeletion).toBeUndefined();
-  expect(wrapper.state().definitionInEdition).toBeUndefined();
-});
-
-it('should delete config', async () => {
-  const onUpdateDefinitions = jest.fn();
-  const wrapper = shallowRender({ onUpdateDefinitions });
-  wrapper.setState({ definitionKeyForDeletion: '123' });
-
-  await wrapper
-    .instance()
-    .deleteConfiguration('123')
-    .then(() => {
-      expect(deleteConfiguration).toBeCalledWith('123');
-      expect(onUpdateDefinitions).toBeCalled();
-      expect(wrapper.state().definitionKeyForDeletion).toBeUndefined();
-    });
+  expect(wrapper.state().editedDefinition).toBeUndefined();
 });
 
 it('should create config', async () => {
   const onUpdateDefinitions = jest.fn();
-  const config = {
-    key: 'new conf',
-    url: 'ewrqewr',
-    appId: '3742985',
-    privateKey: 'rt7r78ew6t87ret'
-  };
+  const config = mockGithubDefinition();
   const wrapper = shallowRender({ onUpdateDefinitions });
-  wrapper.setState({ definitionInEdition: config });
+  wrapper.setState({ editedDefinition: config });
+
+  await wrapper.instance().handleSubmit(config, '');
 
-  await wrapper
-    .instance()
-    .handleSubmit(config, '')
-    .then(() => {
-      expect(createGithubConfiguration).toBeCalledWith(config);
-      expect(onUpdateDefinitions).toBeCalled();
-      expect(wrapper.state().definitionInEdition).toBeUndefined();
-    });
+  expect(createGithubConfiguration).toBeCalledWith(config);
+  expect(onUpdateDefinitions).toBeCalled();
+  expect(wrapper.state().editedDefinition).toBeUndefined();
 });
 
 it('should update config', async () => {
   const onUpdateDefinitions = jest.fn();
-  const config = {
-    key: 'new conf',
-    url: 'ewrqewr',
-    appId: '3742985',
-    privateKey: 'rt7r78ew6t87ret'
-  };
+  const config = mockGithubDefinition();
   const wrapper = shallowRender({ onUpdateDefinitions });
-  wrapper.setState({ definitionInEdition: config });
+  wrapper.setState({ editedDefinition: config });
 
-  await wrapper
-    .instance()
-    .handleSubmit(config, 'originalKey')
-    .then(() => {
-      expect(updateGithubConfiguration).toBeCalledWith({
-        newKey: 'new conf',
-        ...config,
-        key: 'originalKey'
-      });
-      expect(onUpdateDefinitions).toBeCalled();
-      expect(wrapper.state().definitionInEdition).toBeUndefined();
-    });
+  await wrapper.instance().handleSubmit(config, 'originalKey');
+
+  expect(updateGithubConfiguration).toBeCalledWith({
+    newKey: 'key',
+    ...config,
+    key: 'originalKey'
+  });
+  expect(onUpdateDefinitions).toBeCalled();
+  expect(wrapper.state().editedDefinition).toBeUndefined();
 });
 
 function shallowRender(props: Partial<GithubTab['props']> = {}) {
   return shallow<GithubTab>(
-    <GithubTab definitions={[]} onUpdateDefinitions={jest.fn()} {...props} />
+    <GithubTab
+      definitions={[]}
+      loading={false}
+      onDelete={jest.fn()}
+      onUpdateDefinitions={jest.fn()}
+      {...props}
+    />
   );
 }
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTabRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTabRenderer-test.tsx
new file mode 100644 (file)
index 0000000..75e531a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockGithubDefinition } from '../../../../../helpers/testMocks';
+import GithubTabRenderer, { GithubTabRendererProps } from '../GithubTabRenderer';
+
+it('should render correctly', () => {
+  expect(shallowRender({ loading: true })).toMatchSnapshot();
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ editedDefinition: mockGithubDefinition() })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<GithubTabRendererProps> = {}) {
+  return shallow(
+    <GithubTabRenderer
+      definitions={[]}
+      loading={false}
+      onCancel={jest.fn()}
+      onCreate={jest.fn()}
+      onDelete={jest.fn()}
+      onEdit={jest.fn()}
+      onSubmit={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTable-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTable-test.tsx
new file mode 100644 (file)
index 0000000..f8b5b29
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockGithubDefinition } from '../../../../../helpers/testMocks';
+import GithubTable, { GithubTableProps } from '../GithubTable';
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+  expect(shallowRender({ definitions: [mockGithubDefinition()] })).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<GithubTableProps> = {}) {
+  return shallow(
+    <GithubTable
+      definitions={[]}
+      loading={false}
+      onDelete={jest.fn()}
+      onEdit={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PRDecorationTable-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PRDecorationTable-test.tsx
deleted file mode 100644 (file)
index 8fd9643..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { mockGithubDefinition } from '../../../../../helpers/testMocks';
-import { ALM_KEYS } from '../../../utils';
-import PRDecorationTable, { PRDecorationTableProps } from '../PRDecorationTable';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ definitions: [mockGithubDefinition()] })).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<PRDecorationTableProps> = {}) {
-  return shallow(
-    <PRDecorationTable
-      alm={ALM_KEYS.GITHUB}
-      definitions={[]}
-      onDelete={jest.fn()}
-      onEdit={jest.fn()}
-      {...props}
-    />
-  );
-}
index 23c9b71351b307b0dc3b72e93d5d530b4979221b..ef4fb6cd72ebc2bcb14fd4fad2cc0ae09dd72219 100644 (file)
@@ -23,16 +23,21 @@ import { ALM_KEYS } from '../../../utils';
 import PRDecorationTabs, { PRDecorationTabsProps } from '../PRDecorationTabs';
 
 it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ loading: false })).toMatchSnapshot();
+  expect(shallowRender({ loading: true })).toMatchSnapshot();
+  expect(shallowRender({ definitionKeyForDeletion: 'keyToDelete' })).toMatchSnapshot();
+  expect(shallowRender({ currentAlm: ALM_KEYS.AZURE })).toMatchSnapshot();
+  expect(shallowRender({ currentAlm: ALM_KEYS.GITHUB })).toMatchSnapshot();
 });
 
 function shallowRender(props: Partial<PRDecorationTabsProps> = {}) {
   return shallow(
     <PRDecorationTabs
       currentAlm={ALM_KEYS.GITHUB}
-      definitions={{ github: [] }}
-      loading={true}
+      definitions={{ azure: [], github: [] }}
+      loading={false}
+      onCancel={jest.fn()}
+      onConfirmDelete={jest.fn()}
+      onDelete={jest.fn()}
       onSelectAlm={jest.fn()}
       onUpdateDefinitions={jest.fn()}
       {...props}
index abcb76f2613ed383e5dda38b71e3e7f0c4e63860..867ba390e2c3cf98c65f60111181e9f733773d2e 100644 (file)
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import { getAlmDefinitions } from '../../../../../api/almSettings';
+import {
+  countBindedProjects,
+  deleteConfiguration,
+  getAlmDefinitions
+} from '../../../../../api/almSettings';
 import { ALM_KEYS } from '../../../utils';
 import PullRequestDecoration from '../PullRequestDecoration';
 
 jest.mock('../../../../../api/almSettings', () => ({
+  countBindedProjects: jest.fn().mockResolvedValue(0),
+  deleteConfiguration: jest.fn().mockResolvedValue(undefined),
   getAlmDefinitions: jest.fn().mockResolvedValue({ github: [] })
 }));
 
@@ -39,7 +45,7 @@ it('should render correctly', () => {
 it('should handle alm selection', async () => {
   const wrapper = shallowRender();
 
-  wrapper.setState({ currentAlm: ALM_KEYS.BITBUCKET });
+  wrapper.setState({ currentAlm: ALM_KEYS.AZURE });
 
   wrapper.instance().handleSelectAlm(ALM_KEYS.GITHUB);
 
@@ -48,6 +54,28 @@ it('should handle alm selection', async () => {
   expect(wrapper.state().currentAlm).toBe(ALM_KEYS.GITHUB);
 });
 
+it('should handle delete', async () => {
+  const toBeDeleted = '45672';
+  (countBindedProjects as jest.Mock).mockResolvedValueOnce(7);
+  const wrapper = shallowRender();
+
+  wrapper.instance().handleDelete(toBeDeleted);
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.state().projectCount).toBe(7);
+  expect(wrapper.state().definitionKeyForDeletion).toBe(toBeDeleted);
+});
+
+it('should delete configuration', async () => {
+  (deleteConfiguration as jest.Mock).mockResolvedValueOnce(undefined);
+  const wrapper = shallowRender();
+  wrapper.instance().deleteConfiguration('8345678');
+
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().projectCount).toBeUndefined();
+  expect(wrapper.state().definitionKeyForDeletion).toBeUndefined();
+});
+
 it('should fetch settings', async () => {
   const wrapper = shallowRender();
 
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabHeader-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabHeader-test.tsx
new file mode 100644 (file)
index 0000000..a295ab0
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { ALM_KEYS } from '../../../utils';
+import TabHeader, { TabHeaderProps } from '../TabHeader';
+
+it('should render correctly', () => {
+  expect(shallowRender(ALM_KEYS.AZURE)).toMatchSnapshot();
+  expect(shallowRender(ALM_KEYS.GITHUB)).toMatchSnapshot();
+});
+
+function shallowRender(alm: ALM_KEYS, props: Partial<TabHeaderProps> = {}) {
+  return shallow(<TabHeader alm={alm} onCreate={jest.fn()} {...props} />);
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/TabRenderer-test.tsx
deleted file mode 100644 (file)
index eca1676..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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 { mockGithubDefinition } from '../../../../../helpers/testMocks';
-import { ALM_KEYS } from '../../../utils';
-import TabRenderer, { TabRendererProps } from '../TabRenderer';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-  expect(shallowRender({ definitionKeyForDeletion: '123' })).toMatchSnapshot();
-  expect(shallowRender({ definitionInEdition: mockGithubDefinition() })).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<TabRendererProps> = {}) {
-  return shallow(
-    <TabRenderer
-      alm={ALM_KEYS.GITHUB}
-      definitions={[]}
-      onCancel={jest.fn()}
-      onConfirmDelete={jest.fn()}
-      onCreate={jest.fn()}
-      onDelete={jest.fn()}
-      onEdit={jest.fn()}
-      onSubmit={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmDefinitionFormField-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmDefinitionFormField-test.tsx.snap
new file mode 100644 (file)
index 0000000..21d0c24
--- /dev/null
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="modal-field"
+>
+  <label
+    className="display-flex-center"
+    htmlFor="key"
+  >
+    settings.pr_decoration.form.key
+    <em
+      className="mandatory spacer-right"
+    >
+      *
+    </em>
+  </label>
+  <input
+    className="input-super-large"
+    id="key"
+    maxLength={40}
+    name="key"
+    onChange={[Function]}
+    size={50}
+    type="text"
+    value="key"
+  />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+  className="modal-field"
+>
+  <label
+    className="display-flex-center"
+    htmlFor="key"
+  >
+    settings.pr_decoration.form.key
+    <em
+      className="mandatory spacer-right"
+    >
+      *
+    </em>
+    <HelpTooltip
+      overlay="settings.pr_decoration.form.key.help"
+    />
+  </label>
+  <input
+    className="input-super-large"
+    id="key"
+    maxLength={40}
+    name="key"
+    onChange={[Function]}
+    size={50}
+    type="text"
+    value="key"
+  />
+</div>
+`;
+
+exports[`should render correctly 3`] = `
+<div
+  className="modal-field"
+>
+  <label
+    className="display-flex-center"
+    htmlFor="key"
+  >
+    settings.pr_decoration.form.key
+    <em
+      className="mandatory spacer-right"
+    >
+      *
+    </em>
+  </label>
+  <textarea
+    className="settings-large-input"
+    id="privateKey"
+    maxLength={40}
+    onChange={[Function]}
+    required={true}
+    rows={5}
+    value="key"
+  />
+</div>
+`;
index 3a6b897ff39d9771f9ef564ffb417220e69f7bfa..3800e7c93de0ae5096ff9a7b03466298c53a068f 100644 (file)
@@ -2,18 +2,8 @@
 
 exports[`should render correctly 1`] = `
 <AlmPRDecorationFormModalRenderer
-  alm="github"
   canSubmit={[Function]}
-  formData={
-    Object {
-      "appId": "",
-      "key": "",
-      "privateKey": "",
-      "url": "",
-    }
-  }
   onCancel={[MockFunction]}
-  onFieldChange={[Function]}
   onSubmit={[Function]}
   originalKey=""
 />
index dc349c1e09e9257cfc9b62d23f835f2ca1b24fd8..baaeb049227f32bd51e1c4eb032f5dc3f0792f4a 100644 (file)
@@ -20,109 +20,7 @@ exports[`should render correctly 1`] = `
     <div
       className="modal-body modal-container"
     >
-      <div
-        className="modal-field"
-      >
-        <label
-          className="display-flex-center"
-          htmlFor="name"
-        >
-          settings.pr_decoration.form.name
-          <em
-            className="mandatory spacer-right"
-          >
-            *
-          </em>
-          <HelpTooltip
-            overlay="settings.pr_decoration.form.name.help"
-          />
-        </label>
-        <input
-          autoFocus={true}
-          className="input-super-large"
-          id="name"
-          maxLength={40}
-          name="name"
-          onChange={[Function]}
-          size={50}
-          type="text"
-          value="key"
-        />
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          className="display-flex-center"
-          htmlFor="url.github"
-        >
-          settings.pr_decoration.form.url.github
-          <em
-            className="mandatory spacer-right"
-          >
-            *
-          </em>
-        </label>
-        <input
-          className="input-super-large"
-          id="url.github"
-          maxLength={2000}
-          name="url.github"
-          onChange={[Function]}
-          size={50}
-          type="text"
-          value="http:alm.enterprise.com"
-        />
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          className="display-flex-center"
-          htmlFor="app_id"
-        >
-          settings.pr_decoration.form.app_id
-          <em
-            className="mandatory spacer-right"
-          >
-            *
-          </em>
-        </label>
-        <input
-          className="input-super-large"
-          id="app_id"
-          maxLength={80}
-          name="app_id"
-          onChange={[Function]}
-          size={50}
-          type="text"
-          value="123456"
-        />
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          className="display-flex-center"
-          htmlFor="private_key"
-        >
-          settings.pr_decoration.form.private_key
-          <em
-            className="mandatory spacer-right"
-          >
-            *
-          </em>
-        </label>
-        <textarea
-          className="settings-large-input"
-          id="privateKey"
-          maxLength={2000}
-          onChange={[Function]}
-          required={true}
-          rows={5}
-          value="asdf1234"
-        />
-      </div>
+      <Component />
     </div>
     <div
       className="modal-foot"
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureFormModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..ec50eab
--- /dev/null
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <AlmDefinitionFormField
+    autoFocus={true}
+    formData={
+      Object {
+        "key": "",
+        "personalAccessToken": "",
+      }
+    }
+    help={true}
+    id="name"
+    isTextArea={false}
+    maxLength={40}
+    onFieldChange={[MockFunction]}
+    propKey="key"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "key": "",
+        "personalAccessToken": "",
+      }
+    }
+    help={true}
+    id="personal_access_token"
+    isTextArea={true}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="personalAccessToken"
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+  <AlmDefinitionFormField
+    autoFocus={true}
+    formData={
+      Object {
+        "key": "key",
+        "personalAccessToken": "asdf1234",
+      }
+    }
+    help={true}
+    id="name"
+    isTextArea={false}
+    maxLength={40}
+    onFieldChange={[MockFunction]}
+    propKey="key"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "key": "key",
+        "personalAccessToken": "asdf1234",
+      }
+    }
+    help={true}
+    id="personal_access_token"
+    isTextArea={true}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="personalAccessToken"
+  />
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTab-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTab-test.tsx.snap
new file mode 100644 (file)
index 0000000..b80dec0
--- /dev/null
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<AzureTabRenderer
+  definitions={Array []}
+  loading={false}
+  onCancel={[Function]}
+  onCreate={[Function]}
+  onDelete={[MockFunction]}
+  onEdit={[Function]}
+  onSubmit={[Function]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTabRenderer-test.tsx.snap
new file mode 100644 (file)
index 0000000..2e4ab4a
--- /dev/null
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <TabHeader
+    alm="azure"
+    onCreate={[MockFunction]}
+  />
+  <AzureTable
+    definitions={Array []}
+    loading={true}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+  <TabHeader
+    alm="azure"
+    onCreate={[MockFunction]}
+  />
+  <AzureTable
+    definitions={Array []}
+    loading={false}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 3`] = `
+<Fragment>
+  <TabHeader
+    alm="azure"
+    onCreate={[MockFunction]}
+  />
+  <AzureTable
+    definitions={Array []}
+    loading={false}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+  <AlmPRDecorationFormModal
+    bindingDefinition={
+      Object {
+        "key": "key",
+        "personalAccessToken": "asdf1234",
+      }
+    }
+    onCancel={[MockFunction]}
+    onSubmit={[MockFunction]}
+  >
+    <Component />
+  </AlmPRDecorationFormModal>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTable-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTable-test.tsx.snap
new file mode 100644 (file)
index 0000000..05b1adf
--- /dev/null
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<table
+  className="data zebra spacer-bottom"
+>
+  <thead>
+    <tr>
+      <th>
+        settings.pr_decoration.table.column.name
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.edit
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.delete
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td
+        colSpan={3}
+      >
+        settings.pr_decoration.table.empty.azure
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
+
+exports[`should render correctly 2`] = `
+<table
+  className="data zebra spacer-bottom"
+>
+  <thead>
+    <tr>
+      <th>
+        settings.pr_decoration.table.column.name
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.edit
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.delete
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr
+      key="key"
+    >
+      <td>
+        key
+      </td>
+      <td>
+        <ButtonIcon
+          onClick={[Function]}
+        >
+          <EditIcon />
+        </ButtonIcon>
+      </td>
+      <td>
+        <DeleteButton
+          onClick={[Function]}
+        />
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
index bb13dab20ed08338d57227b672229e28b421e85e..710915a2cebbcccf4d9e345b207fdfbd6fdeced8 100644 (file)
@@ -5,6 +5,7 @@ exports[`should render correctly 1`] = `
   confirmButtonText="delete"
   confirmData="1"
   header="settings.pr_decoration.delete.header"
+  isDestructive={true}
   onClose={[MockFunction]}
   onConfirm={[MockFunction]}
 >
@@ -35,6 +36,7 @@ exports[`should render correctly 2`] = `
   confirmButtonText="delete"
   confirmData="1"
   header="settings.pr_decoration.delete.header"
+  isDestructive={true}
   onClose={[MockFunction]}
   onConfirm={[MockFunction]}
 >
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubFormModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubFormModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..d1759a4
--- /dev/null
@@ -0,0 +1,141 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <AlmDefinitionFormField
+    autoFocus={true}
+    formData={
+      Object {
+        "appId": "",
+        "key": "",
+        "privateKey": "",
+        "url": "",
+      }
+    }
+    help={true}
+    id="name"
+    isTextArea={false}
+    maxLength={40}
+    onFieldChange={[MockFunction]}
+    propKey="key"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "",
+        "key": "",
+        "privateKey": "",
+        "url": "",
+      }
+    }
+    help={false}
+    id="url.github"
+    isTextArea={false}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="url"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "",
+        "key": "",
+        "privateKey": "",
+        "url": "",
+      }
+    }
+    help={false}
+    id="app_id"
+    isTextArea={false}
+    maxLength={80}
+    onFieldChange={[MockFunction]}
+    propKey="appId"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "",
+        "key": "",
+        "privateKey": "",
+        "url": "",
+      }
+    }
+    help={false}
+    id="private_key"
+    isTextArea={true}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="privateKey"
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+  <AlmDefinitionFormField
+    autoFocus={true}
+    formData={
+      Object {
+        "appId": "123456",
+        "key": "key",
+        "privateKey": "asdf1234",
+        "url": "http:alm.enterprise.com",
+      }
+    }
+    help={true}
+    id="name"
+    isTextArea={false}
+    maxLength={40}
+    onFieldChange={[MockFunction]}
+    propKey="key"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "123456",
+        "key": "key",
+        "privateKey": "asdf1234",
+        "url": "http:alm.enterprise.com",
+      }
+    }
+    help={false}
+    id="url.github"
+    isTextArea={false}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="url"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "123456",
+        "key": "key",
+        "privateKey": "asdf1234",
+        "url": "http:alm.enterprise.com",
+      }
+    }
+    help={false}
+    id="app_id"
+    isTextArea={false}
+    maxLength={80}
+    onFieldChange={[MockFunction]}
+    propKey="appId"
+  />
+  <AlmDefinitionFormField
+    formData={
+      Object {
+        "appId": "123456",
+        "key": "key",
+        "privateKey": "asdf1234",
+        "url": "http:alm.enterprise.com",
+      }
+    }
+    help={false}
+    id="private_key"
+    isTextArea={true}
+    maxLength={2000}
+    onFieldChange={[MockFunction]}
+    propKey="privateKey"
+  />
+</Fragment>
+`;
index 30354a81600c6ad1c7a9538c95826819e9e7c956..95b9c9ebfb5301de99e9eeec03401ebd86e5e05b 100644 (file)
@@ -1,13 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render correctly 1`] = `
-<TabRenderer
-  alm="github"
+<GithubTabRenderer
   definitions={Array []}
+  loading={false}
   onCancel={[Function]}
-  onConfirmDelete={[Function]}
   onCreate={[Function]}
-  onDelete={[Function]}
+  onDelete={[MockFunction]}
   onEdit={[Function]}
   onSubmit={[Function]}
 />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTabRenderer-test.tsx.snap
new file mode 100644 (file)
index 0000000..a1a75c7
--- /dev/null
@@ -0,0 +1,60 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <TabHeader
+    alm="github"
+    onCreate={[MockFunction]}
+  />
+  <GithubTable
+    definitions={Array []}
+    loading={true}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+  <TabHeader
+    alm="github"
+    onCreate={[MockFunction]}
+  />
+  <GithubTable
+    definitions={Array []}
+    loading={false}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 3`] = `
+<Fragment>
+  <TabHeader
+    alm="github"
+    onCreate={[MockFunction]}
+  />
+  <GithubTable
+    definitions={Array []}
+    loading={false}
+    onDelete={[MockFunction]}
+    onEdit={[MockFunction]}
+  />
+  <AlmPRDecorationFormModal
+    bindingDefinition={
+      Object {
+        "appId": "123456",
+        "key": "key",
+        "privateKey": "asdf1234",
+        "url": "http:alm.enterprise.com",
+      }
+    }
+    onCancel={[MockFunction]}
+    onSubmit={[MockFunction]}
+  >
+    <Component />
+  </AlmPRDecorationFormModal>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTable-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTable-test.tsx.snap
new file mode 100644 (file)
index 0000000..03e8c47
--- /dev/null
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<table
+  className="data zebra spacer-bottom"
+>
+  <thead>
+    <tr>
+      <th>
+        settings.pr_decoration.table.column.name
+      </th>
+      <th>
+        settings.pr_decoration.table.column.github.url
+      </th>
+      <th>
+        settings.pr_decoration.table.column.app_id
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.edit
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.delete
+      </th>
+    </tr>
+  </thead>
+  <tbody />
+</table>
+`;
+
+exports[`should render correctly 2`] = `
+<table
+  className="data zebra spacer-bottom"
+>
+  <thead>
+    <tr>
+      <th>
+        settings.pr_decoration.table.column.name
+      </th>
+      <th>
+        settings.pr_decoration.table.column.github.url
+      </th>
+      <th>
+        settings.pr_decoration.table.column.app_id
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.edit
+      </th>
+      <th
+        className="thin"
+      >
+        settings.pr_decoration.table.column.delete
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr
+      key="key"
+    >
+      <td>
+        key
+      </td>
+      <td>
+        http:alm.enterprise.com
+      </td>
+      <td>
+        123456
+      </td>
+      <td>
+        <ButtonIcon
+          onClick={[Function]}
+        >
+          <EditIcon />
+        </ButtonIcon>
+      </td>
+      <td>
+        <DeleteButton
+          onClick={[Function]}
+        />
+      </td>
+    </tr>
+  </tbody>
+</table>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/PRDecorationTable-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/PRDecorationTable-test.tsx.snap
deleted file mode 100644 (file)
index ee16bf3..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
-  <table
-    className="data zebra spacer-bottom"
-  >
-    <thead>
-      <tr>
-        <th>
-          settings.pr_decoration.table.column.name
-        </th>
-        <th>
-          settings.pr_decoration.table.column.github.url
-        </th>
-        <th>
-          settings.pr_decoration.table.column.app_id
-        </th>
-        <th
-          className="thin"
-        >
-          settings.pr_decoration.table.column.edit
-        </th>
-        <th
-          className="thin"
-        >
-          settings.pr_decoration.table.column.delete
-        </th>
-      </tr>
-    </thead>
-    <tbody />
-  </table>
-</Fragment>
-`;
-
-exports[`should render correctly 2`] = `
-<Fragment>
-  <table
-    className="data zebra spacer-bottom"
-  >
-    <thead>
-      <tr>
-        <th>
-          settings.pr_decoration.table.column.name
-        </th>
-        <th>
-          settings.pr_decoration.table.column.github.url
-        </th>
-        <th>
-          settings.pr_decoration.table.column.app_id
-        </th>
-        <th
-          className="thin"
-        >
-          settings.pr_decoration.table.column.edit
-        </th>
-        <th
-          className="thin"
-        >
-          settings.pr_decoration.table.column.delete
-        </th>
-      </tr>
-    </thead>
-    <tbody>
-      <tr
-        key="key"
-      >
-        <td>
-          key
-        </td>
-        <td>
-          http:alm.enterprise.com
-        </td>
-        <td>
-          123456
-        </td>
-        <td>
-          <ButtonIcon
-            onClick={[Function]}
-          >
-            <EditIcon />
-          </ButtonIcon>
-        </td>
-        <td>
-          <ButtonIcon
-            onClick={[Function]}
-          >
-            <DeleteIcon />
-          </ButtonIcon>
-        </td>
-      </tr>
-    </tbody>
-  </table>
-</Fragment>
-`;
index de9a5288a36b92ed75876b0884da0cadad0801a9..d04c62f26d38bc09eaa7b283cf9cbb6fbc470bad 100644 (file)
@@ -1,9 +1,48 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render correctly 1`] = `
-<DeferredSpinner
-  timeout={100}
-/>
+<Fragment>
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      settings.pr_decoration.title
+    </h1>
+  </header>
+  <div
+    className="markdown small spacer-top big-spacer-bottom"
+  >
+    settings.pr_decoration.description
+  </div>
+  <BoxedTabs
+    onSelect={[MockFunction]}
+    selected="github"
+    tabs={
+      Array [
+        Object {
+          "key": "github",
+          "label": "Github Enterprise",
+        },
+        Object {
+          "key": "azure",
+          "label": "Azure DevOps Server",
+        },
+      ]
+    }
+  />
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <GithubTab
+      definitions={Array []}
+      loading={true}
+      onDelete={[MockFunction]}
+      onUpdateDefinitions={[MockFunction]}
+    />
+  </div>
+</Fragment>
 `;
 
 exports[`should render correctly 2`] = `
@@ -31,6 +70,105 @@ exports[`should render correctly 2`] = `
           "key": "github",
           "label": "Github Enterprise",
         },
+        Object {
+          "key": "azure",
+          "label": "Azure DevOps Server",
+        },
+      ]
+    }
+  />
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <GithubTab
+      definitions={Array []}
+      loading={false}
+      onDelete={[MockFunction]}
+      onUpdateDefinitions={[MockFunction]}
+    />
+  </div>
+  <DeleteModal
+    id="keyToDelete"
+    onCancel={[MockFunction]}
+    onDelete={[MockFunction]}
+  />
+</Fragment>
+`;
+
+exports[`should render correctly 3`] = `
+<Fragment>
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      settings.pr_decoration.title
+    </h1>
+  </header>
+  <div
+    className="markdown small spacer-top big-spacer-bottom"
+  >
+    settings.pr_decoration.description
+  </div>
+  <BoxedTabs
+    onSelect={[MockFunction]}
+    selected="azure"
+    tabs={
+      Array [
+        Object {
+          "key": "github",
+          "label": "Github Enterprise",
+        },
+        Object {
+          "key": "azure",
+          "label": "Azure DevOps Server",
+        },
+      ]
+    }
+  />
+  <div
+    className="boxed-group boxed-group-inner"
+  >
+    <AzureTab
+      definitions={Array []}
+      loading={false}
+      onDelete={[MockFunction]}
+      onUpdateDefinitions={[MockFunction]}
+    />
+  </div>
+</Fragment>
+`;
+
+exports[`should render correctly 4`] = `
+<Fragment>
+  <header
+    className="page-header"
+  >
+    <h1
+      className="page-title"
+    >
+      settings.pr_decoration.title
+    </h1>
+  </header>
+  <div
+    className="markdown small spacer-top big-spacer-bottom"
+  >
+    settings.pr_decoration.description
+  </div>
+  <BoxedTabs
+    onSelect={[MockFunction]}
+    selected="github"
+    tabs={
+      Array [
+        Object {
+          "key": "github",
+          "label": "Github Enterprise",
+        },
+        Object {
+          "key": "azure",
+          "label": "Azure DevOps Server",
+        },
       ]
     }
   />
@@ -39,6 +177,8 @@ exports[`should render correctly 2`] = `
   >
     <GithubTab
       definitions={Array []}
+      loading={false}
+      onDelete={[MockFunction]}
       onUpdateDefinitions={[MockFunction]}
     />
   </div>
index c11cd3f8e940de0aeaf7424522f9ea8f62a418c6..c3fa031b26d415f04fd879119a050f241ae7518f 100644 (file)
@@ -5,10 +5,14 @@ exports[`should render correctly 1`] = `
   currentAlm="github"
   definitions={
     Object {
+      "azure": Array [],
       "github": Array [],
     }
   }
   loading={true}
+  onCancel={[Function]}
+  onConfirmDelete={[Function]}
+  onDelete={[Function]}
   onSelectAlm={[Function]}
   onUpdateDefinitions={[Function]}
 />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabHeader-test.tsx.snap
new file mode 100644 (file)
index 0000000..29b3799
--- /dev/null
@@ -0,0 +1,79 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <Alert
+    className="spacer-top huge-spacer-bottom"
+    variant="info"
+  >
+    <FormattedMessage
+      defaultMessage="settings.pr_decoration.azure.info"
+      id="settings.pr_decoration.azure.info"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            to="/documentation/analysis/pull-request/#pr-decoration"
+          >
+            learn_more
+          </Link>,
+        }
+      }
+    />
+  </Alert>
+  <div
+    className="big-spacer-bottom display-flex-space-between"
+  >
+    <h4
+      className="display-inline"
+    >
+      settings.pr_decoration.table.title
+    </h4>
+    <Button
+      onClick={[MockFunction]}
+    >
+      settings.pr_decoration.table.create
+    </Button>
+  </div>
+</Fragment>
+`;
+
+exports[`should render correctly 2`] = `
+<Fragment>
+  <Alert
+    className="spacer-top huge-spacer-bottom"
+    variant="info"
+  >
+    <FormattedMessage
+      defaultMessage="settings.pr_decoration.github.info"
+      id="settings.pr_decoration.github.info"
+      values={
+        Object {
+          "link": <Link
+            onlyActiveOnIndex={false}
+            style={Object {}}
+            to="/documentation/analysis/pull-request/#pr-decoration"
+          >
+            learn_more
+          </Link>,
+        }
+      }
+    />
+  </Alert>
+  <div
+    className="big-spacer-bottom display-flex-space-between"
+  >
+    <h4
+      className="display-inline"
+    >
+      settings.pr_decoration.table.title
+    </h4>
+    <Button
+      onClick={[MockFunction]}
+    >
+      settings.pr_decoration.table.create
+    </Button>
+  </div>
+</Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/TabRenderer-test.tsx.snap
deleted file mode 100644 (file)
index 79c7479..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
-  <Alert
-    className="spacer-top huge-spacer-bottom"
-    variant="info"
-  >
-    <FormattedMessage
-      defaultMessage="settings.pr_decoration.github.info"
-      id="settings.pr_decoration.github.info"
-      values={
-        Object {
-          "link": <Link
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to="/documentation/analysis/pull-request/#pr-decoration"
-          >
-            learn_more
-          </Link>,
-        }
-      }
-    />
-  </Alert>
-  <div
-    className="big-spacer-bottom display-flex-space-between"
-  >
-    <h4
-      className="display-inline"
-    >
-      settings.pr_decoration.table.title
-    </h4>
-    <Button
-      onClick={[MockFunction]}
-    >
-      settings.pr_decoration.table.create
-    </Button>
-  </div>
-  <PRDecorationTable
-    alm="github"
-    definitions={Array []}
-    onDelete={[MockFunction]}
-    onEdit={[MockFunction]}
-  />
-</Fragment>
-`;
-
-exports[`should render correctly 2`] = `
-<Fragment>
-  <Alert
-    className="spacer-top huge-spacer-bottom"
-    variant="info"
-  >
-    <FormattedMessage
-      defaultMessage="settings.pr_decoration.github.info"
-      id="settings.pr_decoration.github.info"
-      values={
-        Object {
-          "link": <Link
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to="/documentation/analysis/pull-request/#pr-decoration"
-          >
-            learn_more
-          </Link>,
-        }
-      }
-    />
-  </Alert>
-  <div
-    className="big-spacer-bottom display-flex-space-between"
-  >
-    <h4
-      className="display-inline"
-    >
-      settings.pr_decoration.table.title
-    </h4>
-    <Button
-      onClick={[MockFunction]}
-    >
-      settings.pr_decoration.table.create
-    </Button>
-  </div>
-  <PRDecorationTable
-    alm="github"
-    definitions={Array []}
-    onDelete={[MockFunction]}
-    onEdit={[MockFunction]}
-  />
-  <DeleteModal
-    id="123"
-    onCancel={[MockFunction]}
-    onDelete={[MockFunction]}
-  />
-</Fragment>
-`;
-
-exports[`should render correctly 3`] = `
-<Fragment>
-  <Alert
-    className="spacer-top huge-spacer-bottom"
-    variant="info"
-  >
-    <FormattedMessage
-      defaultMessage="settings.pr_decoration.github.info"
-      id="settings.pr_decoration.github.info"
-      values={
-        Object {
-          "link": <Link
-            onlyActiveOnIndex={false}
-            style={Object {}}
-            to="/documentation/analysis/pull-request/#pr-decoration"
-          >
-            learn_more
-          </Link>,
-        }
-      }
-    />
-  </Alert>
-  <div
-    className="big-spacer-bottom display-flex-space-between"
-  >
-    <h4
-      className="display-inline"
-    >
-      settings.pr_decoration.table.title
-    </h4>
-    <Button
-      onClick={[MockFunction]}
-    >
-      settings.pr_decoration.table.create
-    </Button>
-  </div>
-  <PRDecorationTable
-    alm="github"
-    definitions={Array []}
-    onDelete={[MockFunction]}
-    onEdit={[MockFunction]}
-  />
-  <AlmPRDecorationFormModal
-    alm="github"
-    bindingDefinition={
-      Object {
-        "appId": "123456",
-        "key": "key",
-        "privateKey": "asdf1234",
-        "url": "http:alm.enterprise.com",
-      }
-    }
-    onCancel={[MockFunction]}
-    onSubmit={[MockFunction]}
-  />
-</Fragment>
-`;
index 198295b062f644d4a874d67bffa198f9caafc9af..a36aca0c226e3fbd60c447dfbfaf54ef5cc0b632 100644 (file)
@@ -40,6 +40,10 @@ export interface PRDecorationBindingRendererProps {
   success: boolean;
 }
 
+function renderLabel(v: T.AlmSettingsInstance) {
+  return v.url ? `${v.key} — ${v.url}` : v.key;
+}
+
 export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
   const {
     formData: { repository, key },
@@ -100,7 +104,7 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
             clearable={false}
             id="name"
             onChange={({ value }: { value: string }) => props.onFieldChange('key', value)}
-            options={instances.map(v => ({ value: v.key, label: `${v.key} — ${v.url}` }))}
+            options={instances.map(v => ({ value: v.key, label: renderLabel(v) }))}
             searchable={false}
             value={key}
           />
index 118cff7ac85ee21805954b2fb067609c2804a441..8337e33e576e528bac0f59f101e3ff9086c27acc 100644 (file)
@@ -23,14 +23,14 @@ import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n';
 export const DEFAULT_CATEGORY = 'general';
 
 export enum ALM_KEYS {
-  BITBUCKET = 'bitbucket',
-  GITHUB = 'github',
-  AZURE_DEVOPS = 'azure_devops'
+  AZURE = 'azure',
+  // BITBUCKET = 'bitbucket',
+  GITHUB = 'github'
 }
 
 export const almName = {
-  [ALM_KEYS.AZURE_DEVOPS]: 'Azure DevOps Server',
-  [ALM_KEYS.BITBUCKET]: 'Bitbucket Server',
+  [ALM_KEYS.AZURE]: 'Azure DevOps Server',
+  // [ALM_KEYS.BITBUCKET]: 'Bitbucket Server',
   [ALM_KEYS.GITHUB]: 'Github Enterprise'
 };
 
index 43b2a8839df850470b1d61d437ab331ef655da8a..40ea08a4cfcd435d49a5797ca66bfe6a1deb6732 100644 (file)
@@ -50,6 +50,16 @@ export function mockAlmOrganization(overrides: Partial<T.AlmOrganization> = {}):
   };
 }
 
+export function mockAzureDefinition(
+  overrides: Partial<T.AzureBindingDefinition> = {}
+): T.AzureBindingDefinition {
+  return {
+    key: 'key',
+    personalAccessToken: 'asdf1234',
+    ...overrides
+  };
+}
+
 export function mockGithubDefinition(
   overrides: Partial<T.GithubBindingDefinition> = {}
 ): T.GithubBindingDefinition {
index 9e6c7d7d9fe131a12adc71331abe9ab69b6387be..0944092c96f01788f51388c9fa6886e783c3e539 100644 (file)
 declare namespace T {
   export interface AlmSettingsBinding {
     key: string;
-    url: string;
   }
 
-  export interface AlmSettingsInstance extends AlmSettingsBinding {
+  export interface AlmSettingsInstance {
     alm: string;
+    key: string;
+    url?: string;
   }
 
   export interface AlmSettingsBindingDefinitions {
+    azure: AzureBindingDefinition[];
     github: GithubBindingDefinition[];
   }
 
   export interface GithubBindingDefinition extends AlmSettingsBinding {
+    url: string;
     appId: string;
     privateKey: string;
   }
 
+  export interface AzureBindingDefinition extends AlmSettingsBinding {
+    personalAccessToken: string;
+  }
+
   export interface ProjectAlmBinding {
     key: string;
     alm: string;
index 57243749d4479de044ebab0ab417724a4e4c1d8e..5ea5c4e3c7454f8846659304b8e6f5efdaf37cab 100644 (file)
@@ -924,6 +924,7 @@ settings.pr_decoration.category=Pull Requests
 settings.pr_decoration.title=Pull Requests decoration
 settings.pr_decoration.description=When Pull Request decoration is enabled, SonarQube publishes the status of the analysis directly in your ALM Pull requests.
 settings.pr_decoration.manage_instances=Manage instances
+settings.pr_decoration.azure.info=The account that will be used to decorate Pull Requests needs write permission. {link}
 settings.pr_decoration.github.info=You need to install a Github App with specific settings and permissions to enable Pull Request Decoration on your Organization or Repository. {link}
 settings.pr_decoration.table.title=Pull Request decoration configurations
 settings.pr_decoration.table.create=Create configuration
@@ -943,6 +944,8 @@ settings.pr_decoration.form.name.help=Give your configuration a clear and succin
 settings.pr_decoration.form.url.github=GitHub Enterprise URL
 settings.pr_decoration.form.app_id=GitHub App ID
 settings.pr_decoration.form.private_key=Private Key
+settings.pr_decoration.form.personal_access_token=Personal Access token
+settings.pr_decoration.form.personal_access_token.help=Token of the user that will be used to decorate the pull requests. Needs authorized scope: "Code (read and write)".
 settings.pr_decoration.form.save=Save configuration
 settings.pr_decoration.form.cancel=Cancel