]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12515 UI for project-level form - Azure
authorJeremy Davis <jeremy.davis@sonarsource.com>
Mon, 21 Oct 2019 15:36:17 +0000 (17:36 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 6 Nov 2019 09:04:27 +0000 (10:04 +0100)
server/sonar-web/src/main/js/api/almSettings.ts
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap
server/sonar-web/src/main/js/types/alm-settings.d.ts

index 52cf1b61366e43b256fb748b98a27fe61ed034c7..e7aaee5082797480e8e000cd6270e8f8dab24b5c 100644 (file)
@@ -57,13 +57,17 @@ export function countBindedProjects(almSetting: string) {
 }
 
 export function getProjectAlmBinding(project: string): Promise<T.ProjectAlmBinding> {
-  return getJSON('/api/alm_settings/get_github_binding', { project });
+  return getJSON('/api/alm_settings/get_binding', { project });
 }
 
 export function deleteProjectAlmBinding(project: string): Promise<void> {
   return post('/api/alm_settings/delete_binding', { project }).catch(throwGlobalError);
 }
 
-export function setProjectAlmBinding(data: T.GithubProjectAlmBinding) {
+export function setProjectAzureBinding(data: T.AzureProjectAlmBinding) {
+  return post('/api/alm_settings/set_azure_binding', data).catch(throwGlobalError);
+}
+
+export function setProjectGithubBinding(data: T.GithubProjectAlmBinding) {
   return post('/api/alm_settings/set_github_binding', data).catch(throwGlobalError);
 }
index 82482cbcbfc2749aef530cb5146c7709990a6312..6c31725727a229ce9d0a609b6eefa27655880046 100644 (file)
@@ -22,9 +22,11 @@ import {
   deleteProjectAlmBinding,
   getAlmSettings,
   getProjectAlmBinding,
-  setProjectAlmBinding
+  setProjectAzureBinding,
+  setProjectGithubBinding
 } from '../../../../api/almSettings';
 import throwGlobalError from '../../../../app/utils/throwGlobalError';
+import { ALM_KEYS } from '../../utils';
 import PRDecorationBindingRenderer from './PRDecorationBindingRenderer';
 
 interface Props {
@@ -32,7 +34,7 @@ interface Props {
 }
 
 interface State {
-  formData: T.GithubBinding;
+  formData: T.ProjectAlmBinding;
   hasBinding: boolean;
   instances: T.AlmSettingsInstance[];
   isValid: boolean;
@@ -41,6 +43,11 @@ interface State {
   success: boolean;
 }
 
+const FIELDS_BY_ALM: { [almKey: string]: Array<'repository'> } = {
+  [ALM_KEYS.AZURE]: [],
+  [ALM_KEYS.GITHUB]: ['repository']
+};
+
 export default class PRDecorationBinding extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = {
@@ -70,13 +77,16 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
     return Promise.all([getAlmSettings(project), this.getProjectBinding(project)])
       .then(([instances, data]) => {
         if (this.mounted) {
-          this.setState(({ formData }) => ({
-            formData: data || formData,
-            hasBinding: Boolean(data),
-            instances,
-            isValid: this.validateForm(),
-            loading: false
-          }));
+          this.setState(({ formData }) => {
+            const newFormData = data || formData;
+            return {
+              formData: newFormData,
+              hasBinding: Boolean(data),
+              instances,
+              isValid: this.validateForm(newFormData),
+              loading: false
+            };
+          });
 
           if (!data && instances.length === 1) {
             this.handleFieldChange('key', instances[0].key);
@@ -125,18 +135,50 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
       .catch(this.catchError);
   };
 
+  submitProjectAlmBinding(
+    alm: ALM_KEYS,
+    key: string,
+    almSpecificFields?: { repository?: string }
+  ): Promise<void> {
+    const almSetting = key;
+    const project = this.props.component.key;
+
+    switch (alm) {
+      case ALM_KEYS.AZURE:
+        return setProjectAzureBinding({
+          almSetting,
+          project
+        });
+      case ALM_KEYS.GITHUB: {
+        const repository = almSpecificFields && almSpecificFields.repository;
+        if (!repository) {
+          return Promise.reject();
+        }
+        return setProjectGithubBinding({
+          almSetting,
+          project,
+          repository
+        });
+      }
+      default:
+        return Promise.reject();
+    }
+  }
+
   handleSubmit = () => {
     this.setState({ saving: true });
     const {
-      formData: { key, repository }
+      formData: { key, ...additionalFields },
+      instances
     } = this.state;
 
-    if (key && repository) {
-      setProjectAlmBinding({
-        almSetting: key,
-        project: this.props.component.key,
-        repository
-      })
+    const selected = instances.find(i => i.key === key);
+    if (!key || !selected) {
+      return;
+    }
+
+    if (key) {
+      this.submitProjectAlmBinding(selected.alm as ALM_KEYS, key, additionalFields)
         .then(() => {
           if (this.mounted) {
             this.setState({
@@ -150,21 +192,28 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
     }
   };
 
-  handleFieldChange = (id: keyof T.GithubBinding, value: string) => {
-    this.setState(({ formData: formdata }) => ({
-      formData: {
-        ...formdata,
+  handleFieldChange = (id: keyof T.ProjectAlmBinding, value: string) => {
+    this.setState(({ formData }) => {
+      const newFormData = {
+        ...formData,
         [id]: value
-      },
-      isValid: this.validateForm(),
-      success: false
-    }));
+      };
+      return {
+        formData: newFormData,
+        isValid: this.validateForm(newFormData),
+        success: false
+      };
+    });
   };
 
-  validateForm = () => {
-    const { formData } = this.state;
-    return Object.values(formData).reduce(
-      (result: boolean, value) => result && Boolean(value),
+  validateForm = ({ key, ...additionalFields }: State['formData']) => {
+    const { instances } = this.state;
+    const selected = instances.find(i => i.key === key);
+    if (!key || !selected) {
+      return false;
+    }
+    return FIELDS_BY_ALM[selected.alm as ALM_KEYS].reduce(
+      (result: boolean, field) => result && Boolean(additionalFields[field]),
       true
     );
   };
index a36aca0c226e3fbd60c447dfbfaf54ef5cc0b632..3fa7892e3208910a293b1f7bf674d77484b052e1 100644 (file)
@@ -26,14 +26,15 @@ import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon'
 import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
 import { translate } from 'sonar-ui-common/helpers/l10n';
+import { ALM_KEYS } from '../../utils';
 
 export interface PRDecorationBindingRendererProps {
-  formData: T.GithubBinding;
+  formData: T.ProjectAlmBinding;
   hasBinding: boolean;
   instances: T.AlmSettingsInstance[];
   isValid: boolean;
   loading: boolean;
-  onFieldChange: (id: keyof T.GithubBinding, value: string) => void;
+  onFieldChange: (id: keyof T.ProjectAlmBinding, value: string) => void;
   onReset: () => void;
   onSubmit: () => void;
   saving: boolean;
@@ -79,6 +80,9 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
     );
   }
 
+  const selected = key && instances.find(i => i.key === key);
+  const alm = selected && (selected.alm as ALM_KEYS);
+
   return (
     <div>
       <header className="page-header">
@@ -110,7 +114,7 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
           />
         </div>
 
-        {key && (
+        {alm === ALM_KEYS.GITHUB && (
           <div className="form-field">
             <label htmlFor="repository">
               {translate('settings.pr_decoration.binding.form.repository')}
index 4129a868c6f2d90f3f5a174ed790e2df95c9981c..e0e1b899c4acceb3abbb488328022d80a9ee0ab3 100644 (file)
@@ -24,15 +24,18 @@ import {
   deleteProjectAlmBinding,
   getAlmSettings,
   getProjectAlmBinding,
-  setProjectAlmBinding
+  setProjectAzureBinding,
+  setProjectGithubBinding
 } from '../../../../../api/almSettings';
 import { mockComponent } from '../../../../../helpers/testMocks';
+import { ALM_KEYS } from '../../../utils';
 import PRDecorationBinding from '../PRDecorationBinding';
 
 jest.mock('../../../../../api/almSettings', () => ({
   getAlmSettings: jest.fn().mockResolvedValue([]),
   getProjectAlmBinding: jest.fn().mockResolvedValue(undefined),
-  setProjectAlmBinding: jest.fn().mockResolvedValue(undefined),
+  setProjectAzureBinding: jest.fn().mockResolvedValue(undefined),
+  setProjectGithubBinding: jest.fn().mockResolvedValue(undefined),
   deleteProjectAlmBinding: jest.fn().mockResolvedValue(undefined)
 }));
 
@@ -48,7 +51,7 @@ it('should render correctly', () => {
 
 it('should fill selects and fill formdata', async () => {
   const url = 'github.com';
-  const instances = [{ key: 'instance1', url, alm: 'github' }];
+  const instances = [{ key: 'instance1', url, alm: ALM_KEYS.GITHUB }];
   const formdata = {
     key: 'instance1',
     repository: 'account/repo'
@@ -96,18 +99,38 @@ it('should handle reset', async () => {
   expect(wrapper.state().hasBinding).toBe(false);
 });
 
-it('should handle submit', async () => {
+it('should handle submit to github or azure', async () => {
   const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
-  wrapper.setState({ formData });
+  const instances = [
+    { key: 'github', alm: ALM_KEYS.GITHUB },
+    { key: 'azure', alm: ALM_KEYS.AZURE }
+  ];
 
+  // Github
+  const githubKey = 'github';
+  const repository = 'repo/path';
+  wrapper.setState({ formData: { key: githubKey, repository }, instances });
   wrapper.instance().handleSubmit();
   await waitAndUpdate(wrapper);
 
-  expect(setProjectAlmBinding).toBeCalledWith({
-    almSetting: formData.key,
+  expect(setProjectGithubBinding).toBeCalledWith({
+    almSetting: githubKey,
     project: PROJECT_KEY,
-    repository: formData.repository
+    repository
+  });
+  expect(wrapper.state().hasBinding).toBe(true);
+  expect(wrapper.state().success).toBe(true);
+
+  // azure
+  const azureKey = 'azure';
+  wrapper.setState({ formData: { key: azureKey } });
+  wrapper.instance().handleSubmit();
+  await waitAndUpdate(wrapper);
+
+  expect(setProjectAzureBinding).toBeCalledWith({
+    almSetting: azureKey,
+    project: PROJECT_KEY
   });
   expect(wrapper.state().hasBinding).toBe(true);
   expect(wrapper.state().success).toBe(true);
@@ -115,7 +138,7 @@ it('should handle submit', async () => {
 
 it('should handle failures gracefully', async () => {
   (getProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
-  (setProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
+  (setProjectGithubBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
   (deleteProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
 
   const wrapper = shallowRender();
@@ -158,13 +181,15 @@ it('should validate form', async () => {
   const wrapper = shallowRender();
   await waitAndUpdate(wrapper);
 
-  expect(wrapper.instance().validateForm()).toBe(false);
-
-  wrapper.setState({ formData: { key: '', repository: 'c' } });
-  expect(wrapper.instance().validateForm()).toBe(false);
+  expect(wrapper.instance().validateForm({ key: '', repository: '' })).toBe(false);
+  expect(wrapper.instance().validateForm({ key: '', repository: 'c' })).toBe(false);
 
-  wrapper.setState({ formData: { key: 'a', repository: 'c' } });
-  expect(wrapper.instance().validateForm()).toBe(true);
+  wrapper.setState({
+    instances: [{ key: 'azure', alm: ALM_KEYS.AZURE }, { key: 'github', alm: ALM_KEYS.GITHUB }]
+  });
+  expect(wrapper.instance().validateForm({ key: 'azure' })).toBe(true);
+  expect(wrapper.instance().validateForm({ key: 'github', repository: '' })).toBe(false);
+  expect(wrapper.instance().validateForm({ key: 'github', repository: 'asdf' })).toBe(true);
 });
 
 function shallowRender(props: Partial<PRDecorationBinding['props']> = {}) {
index f47e0713fcf85b0a2d2ddfd6e97a40ad12d32d3a..48c7c0982081602c000e80ab33fe2e66022702ce 100644 (file)
@@ -74,7 +74,7 @@ it('should render multiple instances correctly', () => {
   expect(
     shallowRender({
       formData: {
-        key: 'Github - main instance',
+        key: 'i1',
         repository: 'account/repo'
       },
       hasBinding: true,
index 06ad8d9c9c600a86abd8f474266f6359f2a88eb0..a57c9bfa8569e31df0fe292ee3358d5e8f110a0f 100644 (file)
@@ -373,7 +373,7 @@ exports[`should render multiple instances correctly 2`] = `
           ]
         }
         searchable={false}
-        value="Github - main instance"
+        value="i1"
       />
     </div>
     <div
index 0944092c96f01788f51388c9fa6886e783c3e539..16fd0be23da33e95e59302c3699a7bc8a3b59de1 100644 (file)
@@ -45,19 +45,17 @@ declare namespace T {
 
   export interface ProjectAlmBinding {
     key: string;
-    alm: string;
-    url: string;
-    repository: string;
+    repository?: string;
   }
 
-  export interface GithubProjectAlmBinding {
+  export interface AzureProjectAlmBinding {
     almSetting: string;
     project: string;
-    repository: string;
   }
 
-  export interface GithubBinding {
-    key: string;
-    repository?: string;
+  export interface GithubProjectAlmBinding {
+    almSetting: string;
+    project: string;
+    repository: string;
   }
 }