Browse Source

SONAR-13035 Group global ALM related settings into a single category

tags/8.2.0.32929
Wouter Admiraal 4 years ago
parent
commit
3c110148c7
92 changed files with 2407 additions and 1549 deletions
  1. 2
    1
      server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java
  2. 1
    1
      server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java
  3. 0
    0
      server/sonar-web/src/main/js/api/alm-settings.ts
  4. 12
    0
      server/sonar-web/src/main/js/app/styles/init/misc.css
  5. 3
    3
      server/sonar-web/src/main/js/apps/create/project/CreateProjectPageSonarQube.tsx
  6. 2
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx
  7. 2
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx
  8. 2
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx
  9. 4
    4
      server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarQube-test.tsx
  10. 1
    1
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap
  11. 9
    10
      server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx
  12. 1
    1
      server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts
  13. 5
    1
      server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
  14. 4
    4
      server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
  15. 28
    1
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap
  16. 25
    23
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
  17. 12
    16
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionForm.tsx
  18. 5
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx
  19. 16
    18
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormModalRenderer.tsx
  20. 43
    34
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormRenderer.tsx
  21. 16
    17
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionsTable.tsx
  22. 35
    12
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx
  23. 67
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationFeatureBox.tsx
  24. 185
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
  25. 15
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx
  26. 160
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
  27. 5
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx
  28. 25
    14
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx
  29. 7
    7
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketForm.tsx
  30. 114
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx
  31. 5
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/DeleteModal.tsx
  32. 8
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx
  33. 85
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx
  34. 5
    5
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx
  35. 80
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx
  36. 8
    7
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionForm-test.tsx
  37. 9
    4
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormField-test.tsx
  38. 6
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormModalRenderer-test.tsx
  39. 6
    6
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormRenderer-test.tsx
  40. 10
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionsTable-test.tsx
  41. 8
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx
  42. 39
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationFeatureBox-test.tsx
  43. 15
    11
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx
  44. 2
    2
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx
  45. 18
    2
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx
  46. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureForm-test.tsx
  47. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx
  48. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketForm-test.tsx
  49. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx
  50. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/DeleteModal-test.tsx
  51. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubForm-test.tsx
  52. 3
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx
  53. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabForm-test.tsx
  54. 3
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx
  55. 1
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionForm-test.tsx.snap
  56. 3
    3
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap
  57. 31
    15
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormModalRenderer-test.tsx.snap
  58. 161
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormRenderer-test.tsx.snap
  59. 26
    26
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionsTable-test.tsx.snap
  60. 3
    1
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap
  61. 97
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationFeatureBox-test.tsx.snap
  62. 152
    74
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap
  63. 0
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap
  64. 334
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
  65. 8
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap
  66. 42
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap
  67. 12
    12
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap
  68. 112
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
  69. 8
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/DeleteModal-test.tsx.snap
  70. 14
    14
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap
  71. 84
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap
  72. 8
    8
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap
  73. 68
    0
      server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap
  74. 0
    136
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmTabRenderer.tsx
  75. 0
    57
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/BitbucketTab.tsx
  76. 0
    57
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTab.tsx
  77. 0
    51
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GitlabTab.tsx
  78. 0
    186
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTabs.tsx
  79. 0
    121
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationFormRenderer-test.tsx.snap
  80. 0
    254
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
  81. 0
    28
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTab-test.tsx.snap
  82. 0
    40
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap
  83. 0
    44
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTab-test.tsx.snap
  84. 0
    28
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GitlabTab-test.tsx.snap
  85. 12
    12
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx
  86. 3
    3
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
  87. 10
    10
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx
  88. 9
    9
      server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx
  89. 2
    2
      server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts
  90. 23
    23
      server/sonar-web/src/main/js/types/alm-settings.ts
  91. 63
    53
      sonar-core/src/main/resources/org/sonar/l10n/core.properties
  92. 5
    0
      sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java

+ 2
- 1
server/sonar-auth-github/src/main/java/org/sonar/auth/github/GitHubSettings.java View File

import java.util.List; import java.util.List;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration; import org.sonar.api.config.Configuration;
import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinition;




private static final String ORGANIZATIONS = "sonar.auth.github.organizations"; private static final String ORGANIZATIONS = "sonar.auth.github.organizations";


private static final String CATEGORY = "security";
private static final String CATEGORY = CoreProperties.CATEGORY_ALM_INTEGRATION;
private static final String SUBCATEGORY = "github"; private static final String SUBCATEGORY = "github";


private final Configuration configuration; private final Configuration configuration;

+ 1
- 1
server/sonar-auth-gitlab/src/main/java/org/sonar/auth/gitlab/GitLabSettings.java View File

static final String GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP = "sonar.auth.gitlab.allowUsersToSignUp"; static final String GITLAB_AUTH_ALLOW_USERS_TO_SIGNUP = "sonar.auth.gitlab.allowUsersToSignUp";
static final String GITLAB_AUTH_SYNC_USER_GROUPS = "sonar.auth.gitlab.groupsSync"; static final String GITLAB_AUTH_SYNC_USER_GROUPS = "sonar.auth.gitlab.groupsSync";


private static final String CATEGORY = CoreProperties.CATEGORY_SECURITY;
private static final String CATEGORY = CoreProperties.CATEGORY_ALM_INTEGRATION;
private static final String SUBCATEGORY = "gitlab"; private static final String SUBCATEGORY = "gitlab";


private final Configuration configuration; private final Configuration configuration;

server/sonar-web/src/main/js/api/almSettings.ts → server/sonar-web/src/main/js/api/alm-settings.ts View File


+ 12
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css View File

padding-top: calc(var(--gridSize) / 2) !important; padding-top: calc(var(--gridSize) / 2) !important;
} }


.big-padded-top {
padding-top: calc(2 * var(--gridSize));
}

.huge-padded-top {
padding-top: 40px;
}

td.little-spacer-left { td.little-spacer-left {
padding-left: 4px !important; padding-left: 4px !important;
} }
width: 600px !important; width: 600px !important;
} }


.abs-height-100 {
height: 100% !important;
}

.max-height-100 { .max-height-100 {
max-height: 100% !important; max-height: 100% !important;
} }

+ 3
- 3
server/sonar-web/src/main/js/apps/create/project/CreateProjectPageSonarQube.tsx View File

import { WithRouterProps } from 'react-router'; import { WithRouterProps } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { addWhitePageClass, removeWhitePageClass } from 'sonar-ui-common/helpers/pages'; import { addWhitePageClass, removeWhitePageClass } from 'sonar-ui-common/helpers/pages';
import { getAlmSettings } from '../../../api/almSettings';
import { getAlmSettings } from '../../../api/alm-settings';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn'; import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
import { withAppState } from '../../../components/hoc/withAppState'; import { withAppState } from '../../../components/hoc/withAppState';
import { getProjectUrl } from '../../../helpers/urls'; import { getProjectUrl } from '../../../helpers/urls';
import { AlmSettingsInstance, ALM_KEYS } from '../../../types/alm-settings';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import BitbucketProjectCreate from './BitbucketProjectCreate'; import BitbucketProjectCreate from './BitbucketProjectCreate';
import CreateProjectModeSelection from './CreateProjectModeSelection'; import CreateProjectModeSelection from './CreateProjectModeSelection';
import ManualProjectCreate from './ManualProjectCreate'; import ManualProjectCreate from './ManualProjectCreate';
.then(almSettings => { .then(almSettings => {
if (this.mounted) { if (this.mounted) {
this.setState({ this.setState({
bitbucketSettings: almSettings.filter(s => s.alm === ALM_KEYS.BITBUCKET),
bitbucketSettings: almSettings.filter(s => s.alm === AlmKeys.Bitbucket),
loading: false loading: false
}); });
} }

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketPersonalAccessTokenForm-test.tsx View File

import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { AlmKeys } from '../../../../types/alm-settings';
import BitbucketPersonalAccessTokenForm, { import BitbucketPersonalAccessTokenForm, {
BitbucketPersonalAccessTokenFormProps BitbucketPersonalAccessTokenFormProps
} from '../BitbucketPersonalAccessTokenForm'; } from '../BitbucketPersonalAccessTokenForm';
return shallow<BitbucketPersonalAccessTokenFormProps>( return shallow<BitbucketPersonalAccessTokenFormProps>(
<BitbucketPersonalAccessTokenForm <BitbucketPersonalAccessTokenForm
bitbucketSetting={mockAlmSettingsInstance({ bitbucketSetting={mockAlmSettingsInstance({
alm: ALM_KEYS.BITBUCKET,
alm: AlmKeys.Bitbucket,
url: 'http://www.example.com' url: 'http://www.example.com'
})} })}
onPersonalAccessTokenCreate={jest.fn()} onPersonalAccessTokenCreate={jest.fn()}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreate-test.tsx View File

import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations'; import { mockBitbucketRepository } from '../../../../helpers/mocks/alm-integrations';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { mockLocation } from '../../../../helpers/testMocks'; import { mockLocation } from '../../../../helpers/testMocks';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { AlmKeys } from '../../../../types/alm-settings';
import { BitbucketProjectCreate } from '../BitbucketProjectCreate'; import { BitbucketProjectCreate } from '../BitbucketProjectCreate';


jest.mock('../../../../api/alm-integrations', () => { jest.mock('../../../../api/alm-integrations', () => {
function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) { function shallowRender(props: Partial<BitbucketProjectCreate['props']> = {}) {
return shallow<BitbucketProjectCreate>( return shallow<BitbucketProjectCreate>(
<BitbucketProjectCreate <BitbucketProjectCreate
bitbucketSettings={[mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET, key: 'foo' })]}
bitbucketSettings={[mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket, key: 'foo' })]}
loadingBindings={false} loadingBindings={false}
location={mockLocation()} location={mockLocation()}
onProjectCreate={jest.fn()} onProjectCreate={jest.fn()}

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketProjectCreateRenderer-test.tsx View File

mockBitbucketRepository mockBitbucketRepository
} from '../../../../helpers/mocks/alm-integrations'; } from '../../../../helpers/mocks/alm-integrations';
import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { AlmKeys } from '../../../../types/alm-settings';
import BitbucketProjectCreateRenderer, { import BitbucketProjectCreateRenderer, {
BitbucketProjectCreateRendererProps BitbucketProjectCreateRendererProps
} from '../BitbucketProjectCreateRenderer'; } from '../BitbucketProjectCreateRenderer';
function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) { function shallowRender(props: Partial<BitbucketProjectCreateRendererProps> = {}) {
return shallow<BitbucketProjectCreateRendererProps>( return shallow<BitbucketProjectCreateRendererProps>(
<BitbucketProjectCreateRenderer <BitbucketProjectCreateRenderer
bitbucketSetting={mockAlmSettingsInstance({ alm: ALM_KEYS.BITBUCKET })}
bitbucketSetting={mockAlmSettingsInstance({ alm: AlmKeys.Bitbucket })}
importing={false} importing={false}
loading={false} loading={false}
onImportRepository={jest.fn()} onImportRepository={jest.fn()}

+ 4
- 4
server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPageSonarQube-test.tsx View File

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { addWhitePageClass } from 'sonar-ui-common/helpers/pages'; import { addWhitePageClass } from 'sonar-ui-common/helpers/pages';
import { getAlmSettings } from '../../../../api/almSettings';
import { getAlmSettings } from '../../../../api/alm-settings';
import { mockLocation, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks'; import { mockLocation, mockLoggedInUser, mockRouter } from '../../../../helpers/testMocks';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { AlmKeys } from '../../../../types/alm-settings';
import { CreateProjectPageSonarQube } from '../CreateProjectPageSonarQube'; import { CreateProjectPageSonarQube } from '../CreateProjectPageSonarQube';
import { CreateProjectModes } from '../types'; import { CreateProjectModes } from '../types';


jest.mock('../../../../api/almSettings', () => ({
getAlmSettings: jest.fn().mockResolvedValue([{ alm: ALM_KEYS.BITBUCKET, key: 'foo' }])
jest.mock('../../../../api/alm-settings', () => ({
getAlmSettings: jest.fn().mockResolvedValue([{ alm: AlmKeys.Bitbucket, key: 'foo' }])
})); }));


jest.mock('sonar-ui-common/helpers/pages', () => ({ jest.mock('sonar-ui-common/helpers/pages', () => ({

+ 1
- 1
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/BitbucketProjectCreateRenderer-test.tsx.snap View File

Object { Object {
"pathname": "/admin/settings", "pathname": "/admin/settings",
"query": Object { "query": Object {
"category": "pull_request_decoration",
"category": "almintegration",
}, },
} }
} }

+ 9
- 10
server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx View File

import * as React from 'react'; import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { import {
ALM_INTEGRATION,
ANALYSIS_SCOPE_CATEGORY, ANALYSIS_SCOPE_CATEGORY,
LANGUAGES_CATEGORY, LANGUAGES_CATEGORY,
NEW_CODE_PERIOD_CATEGORY, NEW_CODE_PERIOD_CATEGORY,
PULL_REQUEST_DECORATION_BINDING_CATEGORY,
PULL_REQUEST_DECORATION_CATEGORY
PULL_REQUEST_DECORATION_BINDING_CATEGORY
} from './AdditionalCategoryKeys'; } from './AdditionalCategoryKeys';
import AlmIntegration from './almIntegration/AlmIntegration';
import { AnalysisScope } from './AnalysisScope'; import { AnalysisScope } from './AnalysisScope';
import Languages from './Languages'; import Languages from './Languages';
import NewCodePeriod from './NewCodePeriod'; import NewCodePeriod from './NewCodePeriod';
import PullRequestDecoration from './pullRequestDecoration/PullRequestDecoration';
import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding'; import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding';


export interface AdditionalCategoryComponentProps { export interface AdditionalCategoryComponentProps {
displayTab: false displayTab: false
}, },
{ {
key: PULL_REQUEST_DECORATION_CATEGORY,
name: translate('settings.pr_decoration.category'),
renderComponent: getPullRequestDecorationComponent,
key: ALM_INTEGRATION,
name: translate('property.category.almintegration'),
renderComponent: getAlmIntegrationComponent,
availableGlobally: true, availableGlobally: true,
availableForProject: false, availableForProject: false,
displayTab: true,
requiresBranchesEnabled: true
displayTab: false
}, },
{ {
key: PULL_REQUEST_DECORATION_BINDING_CATEGORY, key: PULL_REQUEST_DECORATION_BINDING_CATEGORY,
return <AnalysisScope {...props} />; return <AnalysisScope {...props} />;
} }


function getPullRequestDecorationComponent() {
return <PullRequestDecoration />;
function getAlmIntegrationComponent(props: AdditionalCategoryComponentProps) {
return <AlmIntegration {...props} />;
} }


function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) { function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) {

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts View File

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
export const ALM_INTEGRATION = 'almintegration';
export const ANALYSIS_SCOPE_CATEGORY = 'exclusions'; export const ANALYSIS_SCOPE_CATEGORY = 'exclusions';
export const LANGUAGES_CATEGORY = 'languages'; export const LANGUAGES_CATEGORY = 'languages';
export const NEW_CODE_PERIOD_CATEGORY = 'new_code_period'; export const NEW_CODE_PERIOD_CATEGORY = 'new_code_period';
export const PULL_REQUEST_DECORATION_CATEGORY = 'pull_request_decoration';
export const PULL_REQUEST_DECORATION_BINDING_CATEGORY = 'pull_request_decoration_binding'; export const PULL_REQUEST_DECORATION_BINDING_CATEGORY = 'pull_request_decoration_binding';

+ 5
- 1
server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx View File

component?: T.Component; component?: T.Component;
fetchValues: Function; fetchValues: Function;
settings: Array<T.Setting & { definition: T.SettingCategoryDefinition }>; settings: Array<T.Setting & { definition: T.SettingCategoryDefinition }>;
subCategory?: string;
} }


export default class SubCategoryDefinitionsList extends React.PureComponent<Props> { export default class SubCategoryDefinitionsList extends React.PureComponent<Props> {
const sortedSubCategories = sortBy(subCategories, subCategory => const sortedSubCategories = sortBy(subCategories, subCategory =>
subCategory.name.toLowerCase() subCategory.name.toLowerCase()
); );
const filteredSubCategories = this.props.subCategory
? sortedSubCategories.filter(c => c.key === this.props.subCategory)
: sortedSubCategories;
return ( return (
<ul className="settings-sub-categories-list"> <ul className="settings-sub-categories-list">
{sortedSubCategories.map(subCategory => (
{filteredSubCategories.map(subCategory => (
<li key={subCategory.key}> <li key={subCategory.key}>
<h2 className="settings-sub-category-name">{subCategory.name}</h2> <h2 className="settings-sub-category-name">{subCategory.name}</h2>
{subCategory.description != null && ( {subCategory.description != null && (

+ 4
- 4
server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx View File

import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
import { import {
ALM_INTEGRATION,
ANALYSIS_SCOPE_CATEGORY, ANALYSIS_SCOPE_CATEGORY,
LANGUAGES_CATEGORY, LANGUAGES_CATEGORY,
NEW_CODE_PERIOD_CATEGORY, NEW_CODE_PERIOD_CATEGORY,
PULL_REQUEST_DECORATION_BINDING_CATEGORY,
PULL_REQUEST_DECORATION_CATEGORY
PULL_REQUEST_DECORATION_BINDING_CATEGORY
} from '../AdditionalCategoryKeys'; } from '../AdditionalCategoryKeys';
import { App } from '../AppContainer'; import { App } from '../AppContainer';


expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });


it('should render pull request decoration correctly', async () => {
it('should render ALM integration correctly', async () => {
const wrapper = shallowRender({ const wrapper = shallowRender({
location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_CATEGORY } })
location: mockLocation({ query: { category: ALM_INTEGRATION } })
}); });


await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);

+ 28
- 1
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap View File

/> />
`; `;


exports[`should render additional categories component correctly 4`] = `<PullRequestDecoration />`;
exports[`should render additional categories component correctly 4`] = `
<Connect(withAppState(AlmIntegration))
component={
Object {
"breadcrumbs": Array [],
"key": "my-project",
"name": "MyProject",
"organization": "foo",
"qualifier": "TRK",
"qualityGate": Object {
"isDefault": true,
"key": "30",
"name": "Sonar way",
},
"qualityProfiles": Array [
Object {
"deleted": false,
"key": "my-qp",
"language": "ts",
"name": "Sonar way",
},
],
"tags": Array [],
}
}
selectedCategory="TEST"
/>
`;


exports[`should render additional categories component correctly 5`] = ` exports[`should render additional categories component correctly 5`] = `
<PRDecorationBinding <PRDecorationBinding

+ 25
- 23
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render analysis scope correctly 1`] = `
exports[`should render ALM integration correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="exclusions"
selectedCategory="almintegration"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<AnalysisScope
selectedCategory="exclusions"
<Connect(withAppState(AlmIntegration))
selectedCategory="almintegration"
/> />
</div> </div>
</div> </div>
</div> </div>
`; `;


exports[`should render default view correctly 1`] = `
exports[`should render analysis scope correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="general"
selectedCategory="exclusions"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<Connect(SubCategoryDefinitionsList)
category="general"
<AnalysisScope
selectedCategory="exclusions"
/> />
</div> </div>
</div> </div>
</div> </div>
`; `;


exports[`should render languages correctly 1`] = `
exports[`should render default view correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="languages"
selectedCategory="general"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<withRouter(Connect(Languages))
selectedCategory="languages"
<Connect(SubCategoryDefinitionsList)
category="general"
/> />
</div> </div>
</div> </div>
</div> </div>
`; `;


exports[`should render newCodePeriod correctly 1`] = `
exports[`should render languages correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="new_code_period"
selectedCategory="languages"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<NewCodePeriod />
<withRouter(Connect(Languages))
selectedCategory="languages"
/>
</div> </div>
</div> </div>
</div> </div>
`; `;


exports[`should render pull request decoration binding correctly 1`] = `
exports[`should render newCodePeriod correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="pull_request_decoration_binding"
selectedCategory="new_code_period"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<Connect(SubCategoryDefinitionsList)
category="pull_request_decoration_binding"
/>
<NewCodePeriod />
</div> </div>
</div> </div>
</div> </div>
`; `;


exports[`should render pull request decoration correctly 1`] = `
exports[`should render pull request decoration binding correctly 1`] = `
<div <div
className="page page-limited" className="page page-limited"
id="settings-page" id="settings-page"
> >
<Connect(CategoriesList) <Connect(CategoriesList)
defaultCategory="general" defaultCategory="general"
selectedCategory="pull_request_decoration"
selectedCategory="pull_request_decoration_binding"
/> />
</div> </div>
<div <div
className="side-tabs-main" className="side-tabs-main"
> >
<PullRequestDecoration />
<Connect(SubCategoryDefinitionsList)
category="pull_request_decoration_binding"
/>
</div> </div>
</div> </div>
</div> </div>

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationForm.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionForm.tsx View File

*/ */
import { isEqual, omit } from 'lodash'; import { isEqual, omit } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { AlmSettingsBinding, ALM_KEYS } from '../../../../types/alm-settings';
import AlmPRDecorationFormModalRenderer from './AlmPRDecorationFormModalRenderer';
import AlmPRDecorationFormRenderer from './AlmPRDecorationFormRenderer';
import { AlmBindingDefinition } from '../../../../types/alm-settings';
import AlmBindingDefinitionFormModalRenderer from './AlmBindingDefinitionFormModalRenderer';
import AlmBindingDefinitionFormRenderer from './AlmBindingDefinitionFormRenderer';


export interface AlmPRDecorationFormChildrenProps<B> {
export interface AlmBindingDefinitionFormChildrenProps<B> {
formData: B; formData: B;
hideKeyField?: boolean; hideKeyField?: boolean;
onFieldChange: (fieldId: keyof B, value: string) => void; onFieldChange: (fieldId: keyof B, value: string) => void;
} }


interface Props<B> { interface Props<B> {
alm: ALM_KEYS;
bindingDefinition: B; bindingDefinition: B;
children: (props: AlmPRDecorationFormChildrenProps<B>) => React.ReactNode;
children: (props: AlmBindingDefinitionFormChildrenProps<B>) => React.ReactNode;
help?: React.ReactNode; help?: React.ReactNode;
hideKeyField?: boolean; hideKeyField?: boolean;
loading?: boolean; loading?: boolean;
touched: boolean; touched: boolean;
} }


export default class AlmPRDecorationForm<B extends AlmSettingsBinding> extends React.PureComponent<
Props<B>,
State<B>
> {
export default class AlmBindingDefinitionForm<
B extends AlmBindingDefinition
> extends React.PureComponent<Props<B>, State<B>> {
constructor(props: Props<B>) { constructor(props: Props<B>) {
super(props); super(props);
this.state = { formData: props.bindingDefinition, touched: false }; this.state = { formData: props.bindingDefinition, touched: false };


render() { render() {
const { const {
alm,
bindingDefinition, bindingDefinition,
children, children,
help, help,
const showDelete = showEdit && this.props.onDelete !== undefined; const showDelete = showEdit && this.props.onDelete !== undefined;


return showInModal ? ( return showInModal ? (
<AlmPRDecorationFormModalRenderer
<AlmBindingDefinitionFormModalRenderer
action={bindingDefinition.key ? 'edit' : 'create'} action={bindingDefinition.key ? 'edit' : 'create'}
alm={alm}
canSubmit={this.canSubmit} canSubmit={this.canSubmit}
help={help} help={help}
onCancel={this.handleCancel} onCancel={this.handleCancel}
formData, formData,
onFieldChange: this.handleFieldChange onFieldChange: this.handleFieldChange
})} })}
</AlmPRDecorationFormModalRenderer>
</AlmBindingDefinitionFormModalRenderer>
) : ( ) : (
<AlmPRDecorationFormRenderer
<AlmBindingDefinitionFormRenderer
canSubmit={this.canSubmit} canSubmit={this.canSubmit}
help={help} help={help}
loading={loading} loading={loading}
onFieldChange: this.handleFieldChange, onFieldChange: this.handleFieldChange,
readOnly readOnly
})} })}
</AlmPRDecorationFormRenderer>
</AlmBindingDefinitionFormRenderer>
); );
} }
} }

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmDefinitionFormField.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormField.tsx View File

import * as React from 'react'; import * as React from 'react';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { AlmSettingsBinding } from '../../../../types/alm-settings';
import { AlmBindingDefinition } from '../../../../types/alm-settings';


export interface AlmDefinitionFormFieldProps<B extends AlmSettingsBinding> {
export interface AlmBindingDefinitionFormFieldProps<B extends AlmBindingDefinition> {
autoFocus?: boolean; autoFocus?: boolean;
help?: React.ReactNode; help?: React.ReactNode;
id: string; id: string;
value: string; value: string;
} }


export function AlmDefinitionFormField<B extends AlmSettingsBinding>(
props: AlmDefinitionFormFieldProps<B>
export function AlmBindingDefinitionFormField<B extends AlmBindingDefinition>(
props: AlmBindingDefinitionFormFieldProps<B>
) { ) {
const { const {
autoFocus, autoFocus,
return ( return (
<div className="modal-field"> <div className="modal-field">
<label className="display-flex-center" htmlFor={id}> <label className="display-flex-center" htmlFor={id}>
{translate('settings.pr_decoration.form', id)}
{translate('settings.almintegration.form', id)}
<em className="mandatory spacer-right">*</em> <em className="mandatory spacer-right">*</em>
{help && <HelpTooltip overlay={help} placement="right" />} {help && <HelpTooltip overlay={help} placement="right" />}
</label> </label>

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormModalRenderer.tsx View File

import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { Alert } from 'sonar-ui-common/components/ui/Alert';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { ALM_KEYS } from '../../../../types/alm-settings';


export interface AlmPRDecorationFormModalProps {
export interface AlmBindingDefinitionFormModalProps {
action: 'edit' | 'create'; action: 'edit' | 'create';
alm: ALM_KEYS;
canSubmit: () => boolean; canSubmit: () => boolean;
children: React.ReactNode; children: React.ReactNode;
help?: React.ReactNode; help?: React.ReactNode;
onSubmit: () => void; onSubmit: () => void;
} }


export default function AlmPRDecorationFormModalRenderer(props: AlmPRDecorationFormModalProps) {
const { alm, action, children, help } = props;
const header = translate(
'settings',
alm === ALM_KEYS.GITLAB ? 'mr_decoration' : 'pr_decoration',
'form.header',
action
);
export default function AlmBindingDefinitionFormModalRenderer(
props: AlmBindingDefinitionFormModalProps
) {
const { action, children, help } = props;
const header = translate('settings.almintegration.form.header', action);


return ( return (
<SimpleModal header={header} onClose={props.onCancel} onSubmit={props.onSubmit} size="medium"> <SimpleModal header={header} onClose={props.onCancel} onSubmit={props.onSubmit} size="medium">
</div> </div>


<div className="modal-body modal-container"> <div className="modal-body modal-container">
{help && (
<Alert className="big-spacer-bottom" variant="info">
{help}
</Alert>
)}
{children}
<div className="display-flex-start">
<div className="flex-1">{children}</div>

{help && (
<Alert className="huge-spacer-left flex-1" variant="info">
{help}
</Alert>
)}
</div>
</div> </div>


<div className="modal-foot"> <div className="modal-foot">
<DeferredSpinner className="spacer-right" loading={submitting} /> <DeferredSpinner className="spacer-right" loading={submitting} />
<SubmitButton disabled={submitting || !props.canSubmit()}> <SubmitButton disabled={submitting || !props.canSubmit()}>
{translate('settings.pr_decoration.form.save')}
{translate('settings.almintegration.form.save')}
</SubmitButton> </SubmitButton>
<ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink> <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
</div> </div>

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormRenderer.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionFormRenderer.tsx View File

import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';


export interface AlmPRDecorationFormRendererProps {
export interface AlmBindingDefinitionFormRendererProps {
canSubmit: () => boolean; canSubmit: () => boolean;
children: React.ReactNode; children: React.ReactNode;
help?: React.ReactNode; help?: React.ReactNode;
success: boolean; success: boolean;
} }


export default function AlmPRDecorationFormRenderer(props: AlmPRDecorationFormRendererProps) {
export default function AlmBindingDefinitionFormRenderer(
props: AlmBindingDefinitionFormRendererProps
) {
const { children, help, loading, success } = props; const { children, help, loading, success } = props;


return ( return (
e.preventDefault(); e.preventDefault();
props.onSubmit(); props.onSubmit();
}}> }}>
{help && (
<Alert className="big-spacer-bottom" variant="info">
{help}
</Alert>
)}
<div className="display-flex-start">
<div className="flex-1">
{children}


{children}
<div className="display-flex-center">
{props.onEdit === undefined ? (
<SubmitButton disabled={loading || !props.canSubmit()}>
{translate('settings.almintegration.form.save')}
</SubmitButton>
) : (
<Button disabled={loading} onClick={props.onEdit}>
{translate('edit')}
</Button>
)}
{props.onDelete && (
<Button
className="button-red spacer-left"
disabled={loading}
onClick={props.onDelete}>
{translate('delete')}
</Button>
)}
{props.onCancel && (
<ResetButtonLink className="spacer-left" onClick={props.onCancel}>
{translate('cancel')}
</ResetButtonLink>
)}
{loading && <DeferredSpinner className="spacer-left" />}
{!loading && success && (
<span className="text-success spacer-left">
<AlertSuccessIcon className="spacer-right" />
{translate('settings.state.saved')}
</span>
)}
</div>
</div>


<div className="display-flex-center">
{props.onEdit === undefined ? (
<SubmitButton disabled={loading || !props.canSubmit()}>
{translate('settings.pr_decoration.form.save')}
</SubmitButton>
) : (
<Button disabled={loading} onClick={props.onEdit}>
{translate('edit')}
</Button>
)}
{props.onDelete && (
<Button className="button-red spacer-left" disabled={loading} onClick={props.onDelete}>
{translate('delete')}
</Button>
)}
{props.onCancel && (
<ResetButtonLink className="spacer-left" onClick={props.onCancel}>
{translate('cancel')}
</ResetButtonLink>
)}
{loading && <DeferredSpinner className="spacer-left" />}
{!loading && success && (
<span className="text-success spacer-left">
<AlertSuccessIcon className="spacer-right" />
{translate('settings.state.saved')}
</span>
{help && (
<Alert className="huge-spacer-left flex-1" variant="info">
{help}
</Alert>
)} )}
</div> </div>
</form> </form>

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationTable.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmBindingDefinitionsTable.tsx View File

import { Button, ButtonIcon, DeleteButton } from 'sonar-ui-common/components/controls/buttons'; import { Button, ButtonIcon, DeleteButton } from 'sonar-ui-common/components/controls/buttons';
import EditIcon from 'sonar-ui-common/components/icons/EditIcon'; import EditIcon from 'sonar-ui-common/components/icons/EditIcon';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { ALM_KEYS } from '../../../../types/alm-settings';
import { AlmKeys } from '../../../../types/alm-settings';


export interface AlmPRDecorationTableProps {
export interface AlmBindingDefinitionsTableProps {
additionalColumnsHeaders: Array<string>; additionalColumnsHeaders: Array<string>;
alm: ALM_KEYS;
additionalTableInfo?: React.ReactNode;
alm: AlmKeys;
definitions: Array<{ definitions: Array<{
key: string; key: string;
additionalColumns: Array<string>; additionalColumns: Array<string>;
onEdit: (definitionKey: string) => void; onEdit: (definitionKey: string) => void;
} }


export default function AlmPRDecorationTable(props: AlmPRDecorationTableProps) {
const { additionalColumnsHeaders, alm, definitions } = props;
export default function AlmBindingDefinitionsTable(props: AlmBindingDefinitionsTableProps) {
const { additionalColumnsHeaders, additionalTableInfo, alm, definitions } = props;


return ( return (
<> <>
<div className="spacer-top big-spacer-bottom display-flex-space-between"> <div className="spacer-top big-spacer-bottom display-flex-space-between">
<h4 className="display-inline">
{translate(
'settings',
alm === ALM_KEYS.GITLAB ? 'mr_decoration' : 'pr_decoration',
'table.title'
)}
</h4>
<h2 className="settings-sub-category-name">
{translate('settings.almintegration.table.title')}
</h2>
<Button data-test="settings__alm-create" onClick={props.onCreate}> <Button data-test="settings__alm-create" onClick={props.onCreate}>
{translate('settings.pr_decoration.table.create')}
{translate('settings.almintegration.table.create')}
</Button> </Button>
</div> </div>


{additionalTableInfo}

<table className="data zebra fixed spacer-bottom"> <table className="data zebra fixed spacer-bottom">
<thead> <thead>
<tr> <tr>
<th>{translate('settings.pr_decoration.table.column.name')}</th>
<th>{translate('settings.almintegration.table.column.name')}</th>
{additionalColumnsHeaders.map(h => ( {additionalColumnsHeaders.map(h => (
<th key={h}>{h}</th> <th key={h}>{h}</th>
))} ))}
<th className="action-small text-center"> <th className="action-small text-center">
{translate('settings.pr_decoration.table.column.edit')}
{translate('settings.almintegration.table.column.edit')}
</th> </th>
<th className="action text-center"> <th className="action text-center">
{translate('settings.pr_decoration.table.column.delete')}
{translate('settings.almintegration.table.column.delete')}
</th> </th>
</tr> </tr>
</thead> </thead>
{definitions.length === 0 ? ( {definitions.length === 0 ? (
<tr data-test="settings__alm-empty-table"> <tr data-test="settings__alm-empty-table">
<td colSpan={3 + additionalColumnsHeaders.length}> <td colSpan={3 + additionalColumnsHeaders.length}>
{translate('settings.pr_decoration.table.empty', alm)}
{translate('settings.almintegration.table.empty', alm)}
</td> </td>
</tr> </tr>
) : ( ) : (

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PullRequestDecoration.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegration.tsx View File

countBindedProjects, countBindedProjects,
deleteConfiguration, deleteConfiguration,
getAlmDefinitions getAlmDefinitions
} from '../../../../api/almSettings';
import { AlmSettingsBindingDefinitions, ALM_KEYS } from '../../../../types/alm-settings';
import PRDecorationTabs from './PRDecorationTabs';
} from '../../../../api/alm-settings';
import { withAppState } from '../../../../components/hoc/withAppState';
import { AlmKeys, AlmSettingsBindingDefinitions } from '../../../../types/alm-settings';
import AlmIntegrationRenderer from './AlmIntegrationRenderer';

interface Props {
appState: Pick<T.AppState, 'branchesEnabled' | 'multipleAlmEnabled'>;
component?: T.Component;
}


interface State { interface State {
currentAlm: ALM_KEYS;
currentAlm: AlmKeys;
definitionKeyForDeletion?: string; definitionKeyForDeletion?: string;
definitions: AlmSettingsBindingDefinitions; definitions: AlmSettingsBindingDefinitions;
loading: boolean; loading: boolean;
projectCount?: number; projectCount?: number;
} }


export default class PullRequestDecoration extends React.PureComponent<{}, State> {
export class AlmIntegration extends React.PureComponent<Props, State> {
mounted = false; mounted = false;
state: State = { state: State = {
currentAlm: ALM_KEYS.GITHUB,
currentAlm: AlmKeys.GitHub,
definitions: { definitions: {
[ALM_KEYS.AZURE]: [],
[ALM_KEYS.BITBUCKET]: [],
[ALM_KEYS.GITHUB]: [],
[ALM_KEYS.GITLAB]: []
[AlmKeys.Azure]: [],
[AlmKeys.Bitbucket]: [],
[AlmKeys.GitHub]: [],
[AlmKeys.GitLab]: []
}, },
loading: true loading: true
}; };
}; };


fetchPullRequestDecorationSetting = () => { fetchPullRequestDecorationSetting = () => {
const {
appState: { branchesEnabled }
} = this.props;

if (!branchesEnabled) {
return Promise.resolve();
}

this.setState({ loading: true }); this.setState({ loading: true });
return getAlmDefinitions() return getAlmDefinitions()
.then(definitions => { .then(definitions => {
}); });
}; };


handleSelectAlm = (currentAlm: ALM_KEYS) => {
handleSelectAlm = (currentAlm: AlmKeys) => {
this.setState({ currentAlm }); this.setState({ currentAlm });
}; };


}; };


render() { render() {
const {
appState: { branchesEnabled, multipleAlmEnabled },
component
} = this.props;
return ( return (
<PRDecorationTabs
<AlmIntegrationRenderer
branchesEnabled={Boolean(branchesEnabled)}
component={component}
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onCancel={this.handleCancel} onCancel={this.handleCancel}
onConfirmDelete={this.deleteConfiguration} onConfirmDelete={this.deleteConfiguration}
onDelete={this.handleDelete} onDelete={this.handleDelete}
); );
} }
} }

export default withAppState(AlmIntegration);

+ 67
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationFeatureBox.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as classNames from 'classnames';
import * as React from 'react';
import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon';
import ClearIcon from 'sonar-ui-common/components/icons/ClearIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { colors } from '../../../../app/theme';

export interface AlmIntegrationFeatureBoxProps {
active: boolean;
description: React.ReactNode;
inactiveReason?: React.ReactNode;
name: React.ReactNode;
}

export default function AlmIntegrationFeatureBox(props: AlmIntegrationFeatureBoxProps) {
const { active, description, inactiveReason, name } = props;

return (
<div
className={classNames(
'boxed-group-inner display-flex-start width-30 spacer-right spacer-bottom bordered',
{
'bg-muted': !active
}
)}>
{active ? (
<CheckIcon className="little-spacer-top spacer-right" fill={colors.green} />
) : (
<ClearIcon className="little-spacer-top spacer-right" fill={colors.gray60} />
)}
<div className="display-flex-column abs-height-100">
<h4>{name}</h4>

<div className="spacer-top flex-1">{description}</div>

<div className="spacer-top">
{active ? (
<em className="text-success">{translate('settings.almintegration.feature.enabled')}</em>
) : (
<em className="text-muted">
{inactiveReason || translate('settings.almintegration.feature.disabled')}
</em>
)}
</div>
</div>
</div>
);
}

+ 185
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
import { AlmKeys, AlmSettingsBindingDefinitions } from '../../../../types/alm-settings';
import AzureTab from './AzureTab';
import BitbucketTab from './BitbucketTab';
import DeleteModal from './DeleteModal';
import GithubTab from './GithubTab';
import GitlabTab from './GitlabTab';

export interface AlmIntegrationRendererProps {
branchesEnabled: boolean;
component?: T.Component;
currentAlm: AlmKeys;
definitionKeyForDeletion?: string;
definitions: AlmSettingsBindingDefinitions;
loading: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
onConfirmDelete: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onSelectAlm: (alm: AlmKeys) => void;
onUpdateDefinitions: () => void;
projectCount?: number;
}

const tabs = [
{
key: AlmKeys.GitHub,
label: (
<>
<img
alt="github"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/github.svg`}
/>
GitHub
</>
)
},
{
key: AlmKeys.Bitbucket,
label: (
<>
<img
alt="bitbucket"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
/>
Bitbucket Server
</>
),
requiresBranchesEnabled: true
},
{
key: AlmKeys.Azure,
label: (
<>
<img
alt="azure"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/azure.svg`}
/>
Azure DevOps Server
</>
),
requiresBranchesEnabled: true
},
{
key: AlmKeys.GitLab,
label: (
<>
<img
alt="gitlab"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/gitlab.svg`}
/>
GitLab
</>
)
}
];

export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProps) {
const {
component,
definitionKeyForDeletion,
definitions,
currentAlm,
loading,
branchesEnabled,
multipleAlmEnabled,
projectCount
} = props;

return (
<>
<header className="page-header">
<h1 className="page-title">{translate('settings.almintegration.title')}</h1>
</header>

<div className="markdown small spacer-top big-spacer-bottom">
{translate('settings.almintegration.description')}
</div>
<BoxedTabs
onSelect={props.onSelectAlm}
selected={currentAlm}
tabs={tabs.filter(tab => !(tab.requiresBranchesEnabled && !branchesEnabled))}
/>

{currentAlm === AlmKeys.Azure && (
<AzureTab
definitions={definitions.azure}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === AlmKeys.Bitbucket && (
<BitbucketTab
definitions={definitions.bitbucket}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === AlmKeys.GitHub && (
<GithubTab
branchesEnabled={branchesEnabled}
component={component}
definitions={definitions.github}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === AlmKeys.GitLab && (
<GitlabTab
branchesEnabled={branchesEnabled}
definitions={definitions.gitlab}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}

{definitionKeyForDeletion && (
<DeleteModal
id={definitionKeyForDeletion}
onCancel={props.onCancel}
onDelete={props.onConfirmDelete}
projectCount={projectCount}
/>
)}
</>
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmTab.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTab.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { AlmSettingsBinding, ALM_KEYS } from '../../../../types/alm-settings';
import { AlmPRDecorationFormChildrenProps } from './AlmPRDecorationForm';
import { AlmBindingDefinition, AlmKeys } from '../../../../types/alm-settings';
import { AlmBindingDefinitionFormChildrenProps } from './AlmBindingDefinitionForm';
import { AlmIntegrationFeatureBoxProps } from './AlmIntegrationFeatureBox';
import AlmTabRenderer from './AlmTabRenderer'; import AlmTabRenderer from './AlmTabRenderer';


interface Props<B> { interface Props<B> {
alm: ALM_KEYS;
alm: AlmKeys;
additionalColumnsHeaders?: string[]; additionalColumnsHeaders?: string[];
additionalColumnsKeys?: Array<keyof B>; additionalColumnsKeys?: Array<keyof B>;
additionalTableInfo?: React.ReactNode;
createConfiguration: (data: B) => Promise<void>; createConfiguration: (data: B) => Promise<void>;
defaultBinding: B; defaultBinding: B;
definitions: B[]; definitions: B[];
form: (props: AlmPRDecorationFormChildrenProps<B>) => React.ReactNode;
features?: AlmIntegrationFeatureBoxProps[];
form: (props: AlmBindingDefinitionFormChildrenProps<B>) => React.ReactNode;
help?: React.ReactNode;
loading: boolean; loading: boolean;
multipleAlmEnabled: boolean; multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void; onDelete: (definitionKey: string) => void;
success: boolean; success: boolean;
} }


export default class AlmTab<B extends AlmSettingsBinding> extends React.PureComponent<
export default class AlmTab<B extends AlmBindingDefinition> extends React.PureComponent<
Props<B>, Props<B>,
State<B> State<B>
> { > {
const { const {
additionalColumnsHeaders = [], additionalColumnsHeaders = [],
additionalColumnsKeys = [], additionalColumnsKeys = [],
additionalTableInfo,
alm, alm,
defaultBinding, defaultBinding,
definitions, definitions,
features,
form, form,
help,
loading, loading,
multipleAlmEnabled multipleAlmEnabled
} = this.props; } = this.props;
<AlmTabRenderer <AlmTabRenderer
additionalColumnsHeaders={additionalColumnsHeaders} additionalColumnsHeaders={additionalColumnsHeaders}
additionalColumnsKeys={additionalColumnsKeys} additionalColumnsKeys={additionalColumnsKeys}
additionalTableInfo={additionalTableInfo}
alm={alm} alm={alm}
defaultBinding={defaultBinding} defaultBinding={defaultBinding}
definitions={definitions} definitions={definitions}
editedDefinition={editedDefinition} editedDefinition={editedDefinition}
features={features}
form={form} form={form}
help={help}
loading={loading || submitting} loading={loading || submitting}
multipleAlmEnabled={multipleAlmEnabled} multipleAlmEnabled={multipleAlmEnabled}
onCancel={this.handleCancel} onCancel={this.handleCancel}

+ 160
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { AlmBindingDefinition, AlmKeys } from '../../../../types/alm-settings';
import AlmBindingDefinitionForm, {
AlmBindingDefinitionFormChildrenProps
} from './AlmBindingDefinitionForm';
import AlmBindingDefinitionsTable from './AlmBindingDefinitionsTable';
import AlmIntegrationFeatureBox, {
AlmIntegrationFeatureBoxProps
} from './AlmIntegrationFeatureBox';

export interface AlmTabRendererProps<B> {
additionalColumnsHeaders: string[];
additionalColumnsKeys: Array<keyof B>;
additionalTableInfo?: React.ReactNode;
alm: AlmKeys;
editedDefinition?: B;
defaultBinding: B;
definitions: B[];
features?: AlmIntegrationFeatureBoxProps[];
form: (props: AlmBindingDefinitionFormChildrenProps<B>) => React.ReactNode;
help?: React.ReactNode;
loading: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
onCreate: () => void;
onDelete: (definitionKey: string) => void;
onEdit: (definitionKey: string) => void;
onSubmit: (config: B, originalKey: string) => void;
success: boolean;
}

export default function AlmTabRenderer<B extends AlmBindingDefinition>(
props: AlmTabRendererProps<B>
) {
const {
additionalColumnsHeaders,
additionalColumnsKeys,
additionalTableInfo,
alm,
defaultBinding,
definitions,
editedDefinition,
features = [],
form,
loading,
multipleAlmEnabled,
success,
help = (
<FormattedMessage
defaultMessage={translate(`settings.almintegration.${alm}.info`)}
id={`settings.almintegration.${alm}.info`}
values={{
link: (
<Link target="_blank" to="/documentation/analysis/pr-decoration/">
{translate('learn_more')}
</Link>
)
}}
/>
)
} = props;

let definition: B | undefined;
let mappedDefinitions: Array<{ key: string; additionalColumns: string[] }> = [];
let showEdit: boolean | undefined;

if (!multipleAlmEnabled) {
definition = editedDefinition;
if (definition === undefined && definitions.length > 0) {
definition = definitions[0];
}
showEdit = definition && editedDefinition === undefined;
} else {
mappedDefinitions = definitions.map(({ key, ...properties }) => {
const additionalColumns = additionalColumnsKeys.map(k => (properties as any)[k]);
return {
key,
additionalColumns
};
});
}

return (
<div className="big-padded">
{multipleAlmEnabled ? (
<DeferredSpinner loading={loading}>
<AlmBindingDefinitionsTable
additionalColumnsHeaders={additionalColumnsHeaders}
additionalTableInfo={additionalTableInfo}
alm={alm}
definitions={mappedDefinitions}
onCreate={props.onCreate}
onDelete={props.onDelete}
onEdit={props.onEdit}
/>

{editedDefinition && (
<AlmBindingDefinitionForm
bindingDefinition={editedDefinition}
help={help}
onCancel={props.onCancel}
onSubmit={props.onSubmit}
showInModal={true}>
{form}
</AlmBindingDefinitionForm>
)}
</DeferredSpinner>
) : (
<AlmBindingDefinitionForm
bindingDefinition={definition || defaultBinding}
help={help}
hideKeyField={true}
loading={loading}
onCancel={props.onCancel}
onDelete={definition ? props.onDelete : undefined}
onEdit={showEdit ? props.onEdit : undefined}
onSubmit={props.onSubmit}
readOnly={showEdit}
success={success}>
{form}
</AlmBindingDefinitionForm>
)}

{features.length > 0 && (
<div className="big-spacer-top big-padded-top bordered-top">
<h3 className="big-spacer-bottom">{translate('settings.almintegration.features')}</h3>

<div className="display-flex-wrap">
{features.map((feature, i) => (
<AlmIntegrationFeatureBox key={i} {...feature} />
))}
</div>
</div>
)}
</div>
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureForm.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureForm.tsx View File

import * as React from 'react'; import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { AzureBindingDefinition } from '../../../../types/alm-settings'; import { AzureBindingDefinition } from '../../../../types/alm-settings';
import { AlmDefinitionFormField } from './AlmDefinitionFormField';
import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField';


export interface AzureFormProps { export interface AzureFormProps {
formData: AzureBindingDefinition; formData: AzureBindingDefinition;
return ( return (
<> <>
{!hideKeyField && ( {!hideKeyField && (
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help={translate('settings.pr_decoration.form.name.azure.help')}
help={translate('settings.almintegration.form.name.azure.help')}
id="name.azure" id="name.azure"
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
propKey="key" propKey="key"
value={formData.key} value={formData.key}
/> />
)} )}
<AlmDefinitionFormField
help={translate('settings.pr_decoration.form.personal_access_token.azure.help')}
<AlmBindingDefinitionFormField
help={translate('settings.almintegration.form.personal_access_token.azure.help')}
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AzureTab.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/AzureTab.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { createAzureConfiguration, updateAzureConfiguration } from '../../../../api/almSettings';
import { ALM_KEYS, AzureBindingDefinition } from '../../../../types/alm-settings';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createAzureConfiguration, updateAzureConfiguration } from '../../../../api/alm-settings';
import { AlmKeys, AzureBindingDefinition } from '../../../../types/alm-settings';
import AlmTab from './AlmTab'; import AlmTab from './AlmTab';
import AzureForm from './AzureForm'; import AzureForm from './AzureForm';


const { multipleAlmEnabled, definitions, loading } = props; const { multipleAlmEnabled, definitions, loading } = props;


return ( return (
<AlmTab
alm={ALM_KEYS.AZURE}
createConfiguration={createAzureConfiguration}
defaultBinding={{ key: '', personalAccessToken: '' }}
definitions={definitions}
form={childProps => <AzureForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateAzureConfiguration}
/>
<div className="bordered">
<AlmTab
alm={AlmKeys.Azure}
createConfiguration={createAzureConfiguration}
defaultBinding={{ key: '', personalAccessToken: '' }}
definitions={definitions}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
active: definitions.length > 0,
description: translate('settings.almintegration.feature.pr_decoration.description'),
inactiveReason: translate('settings.almintegration.feature.need_at_least_1_binding')
}
]}
form={childProps => <AzureForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateAzureConfiguration}
/>
</div>
); );
} }

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/BitbucketForm.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketForm.tsx View File

import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { BitbucketBindingDefinition } from '../../../../types/alm-settings'; import { BitbucketBindingDefinition } from '../../../../types/alm-settings';
import { AlmDefinitionFormField } from './AlmDefinitionFormField';
import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField';


export interface BitbucketFormProps { export interface BitbucketFormProps {
formData: BitbucketBindingDefinition; formData: BitbucketBindingDefinition;
return ( return (
<> <>
{!hideKeyField && ( {!hideKeyField && (
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help={translate('settings.pr_decoration.form.name.bitbucket.help')}
help={translate('settings.almintegration.form.name.bitbucket.help')}
id="name.bitbucket" id="name.bitbucket"
maxLength={100} maxLength={100}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
value={formData.key} value={formData.key}
/> />
)} )}
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<FormattedMessage <FormattedMessage
defaultMessage={translate('settings.pr_decoration.form.url.bitbucket.help')}
id="settings.pr_decoration.form.url.bitbucket.help"
defaultMessage={translate('settings.almintegration.form.url.bitbucket.help')}
id="settings.almintegration.form.url.bitbucket.help"
values={{ example: 'https://bitbucket-server.your-company.com' }} values={{ example: 'https://bitbucket-server.your-company.com' }}
/> />
} }
readOnly={readOnly} readOnly={readOnly}
value={formData.url} value={formData.url}
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}

+ 114
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/BitbucketTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import {
createBitbucketConfiguration,
updateBitbucketConfiguration
} from '../../../../api/alm-settings';
import { AlmKeys, BitbucketBindingDefinition } from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import BitbucketForm from './BitbucketForm';

export interface BitbucketTabProps {
definitions: BitbucketBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function BitbucketTab(props: BitbucketTabProps) {
const { multipleAlmEnabled, definitions, loading } = props;

return (
<div className="bordered">
<AlmTab
additionalColumnsHeaders={[translate('settings.almintegration.table.column.bitbucket.url')]}
additionalColumnsKeys={['url']}
additionalTableInfo={
<Alert className="big-spacer-bottom width-50" variant="info">
<FormattedMessage
defaultMessage={translate(
'settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances'
)}
id="settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances"
values={{
feature: (
<em>{translate('settings.almintegration.feature.alm_repo_import.title')}</em>
)
}}
/>
</Alert>
}
alm={AlmKeys.Bitbucket}
createConfiguration={createBitbucketConfiguration}
defaultBinding={{ key: '', url: '', personalAccessToken: '' }}
definitions={definitions}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
active: definitions.length > 0,
description: translate('settings.almintegration.feature.pr_decoration.description'),
inactiveReason: translate('settings.almintegration.feature.need_at_least_1_binding')
},
{
name: translate('settings.almintegration.feature.alm_repo_import.title'),
active: definitions.length === 1,
description: translate('settings.almintegration.feature.alm_repo_import.description'),
inactiveReason: translateWithParameters(
'onboarding.create_project.too_many_bbs_instances_X',
definitions.length
)
}
]}
form={childProps => <BitbucketForm {...childProps} />}
help={
<>
<h3>{translate('onboarding.create_project.pat_help.title')}</h3>

<p className="big-spacer-top">
{translate('settings.almintegration.bitbucket.help_1')}
</p>

<ul className="big-spacer-top list-styled">
<li>{translate('settings.almintegration.bitbucket.help_2')}</li>
<li>{translate('settings.almintegration.bitbucket.help_3')}</li>
</ul>

<p className="big-spacer-top big-spacer-bottom">
<Link target="_blank" to="/documentation/analysis/pr-decoration/">
{translate('learn_more')}
</Link>
</p>
</>
}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateBitbucketConfiguration}
/>
</div>
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/DeleteModal.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/DeleteModal.tsx View File



function showProjectCountWarning(projectCount?: number) { function showProjectCountWarning(projectCount?: number) {
if (projectCount === undefined) { if (projectCount === undefined) {
return <p>{translate('settings.pr_decoration.delete.no_info')}</p>;
return <p>{translate('settings.almintegration.delete.no_info')}</p>;
} }


return projectCount ? ( return projectCount ? (
<p>{translateWithParameters('settings.pr_decoration.delete.info', projectCount)} </p>
<p>{translateWithParameters('settings.almintegration.delete.info', projectCount)} </p>
) : null; ) : null;
} }


<ConfirmModal <ConfirmModal
confirmButtonText={translate('delete')} confirmButtonText={translate('delete')}
confirmData={id} confirmData={id}
header={translate('settings.pr_decoration.delete.header')}
header={translate('settings.almintegration.delete.header')}
isDestructive={true} isDestructive={true}
onClose={onCancel} onClose={onCancel}
onConfirm={onDelete}> onConfirm={onDelete}>
<> <>
<p className="spacer-bottom"> <p className="spacer-bottom">
<FormattedMessage <FormattedMessage
defaultMessage={translate('settings.pr_decoration.delete.message')}
id="settings.pr_decoration.delete.message"
defaultMessage={translate('settings.almintegration.delete.message')}
id="settings.almintegration.delete.message"
values={{ id: <b>{id}</b> }} values={{ id: <b>{id}</b> }}
/> />
</p> </p>

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubForm.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubForm.tsx View File

import * as React from 'react'; import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { GithubBindingDefinition } from '../../../../types/alm-settings'; import { GithubBindingDefinition } from '../../../../types/alm-settings';
import { AlmDefinitionFormField } from './AlmDefinitionFormField';
import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField';


export interface GithubFormProps { export interface GithubFormProps {
formData: GithubBindingDefinition; formData: GithubBindingDefinition;
return ( return (
<> <>
{!hideKeyField && ( {!hideKeyField && (
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help={translate('settings.pr_decoration.form.name.github.help')}
help={translate('settings.almintegration.form.name.github.help')}
id="name.github" id="name.github"
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
propKey="key" propKey="key"
value={formData.key} value={formData.key}
/> />
)} )}
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<> <>
{translate('settings.pr_decoration.form.url.github.help1')}
{translate('settings.almintegration.form.url.github.help1')}
<br /> <br />
<em>https://github.company.com/api/v3</em> <em>https://github.company.com/api/v3</em>
<br /> <br />
<br /> <br />
{translate('settings.pr_decoration.form.url.github.help2')}
{translate('settings.almintegration.form.url.github.help2')}
<br /> <br />
<em>https://api.github.com/</em> <em>https://api.github.com/</em>
</> </>
readOnly={readOnly} readOnly={readOnly}
value={formData.url} value={formData.url}
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="app_id" id="app_id"
maxLength={80} maxLength={80}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
readOnly={readOnly} readOnly={readOnly}
value={formData.appId} value={formData.appId}
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="private_key" id="private_key"
isTextArea={true} isTextArea={true}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}

+ 85
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/GithubTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createGithubConfiguration, updateGithubConfiguration } from '../../../../api/alm-settings';
import { AlmKeys, GithubBindingDefinition } from '../../../../types/alm-settings';
import { ALM_INTEGRATION } from '../AdditionalCategoryKeys';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
import AlmTab from './AlmTab';
import GithubForm from './GithubForm';

export interface GithubTabProps {
branchesEnabled: boolean;
component?: T.Component;
definitions: GithubBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function GithubTab(props: GithubTabProps) {
const { branchesEnabled, component, multipleAlmEnabled, definitions, loading } = props;

return (
<div className="bordered">
{branchesEnabled && (
<>
<AlmTab
additionalColumnsHeaders={[
translate('settings.almintegration.table.column.github.url'),
translate('settings.almintegration.table.column.app_id')
]}
additionalColumnsKeys={['appId', 'url']}
alm={AlmKeys.GitHub}
createConfiguration={createGithubConfiguration}
defaultBinding={{ key: '', appId: '', url: '', privateKey: '' }}
definitions={definitions}
features={[
{
name: translate('settings.almintegration.feature.pr_decoration.title'),
active: definitions.length > 0,
description: translate('settings.almintegration.feature.pr_decoration.description'),
inactiveReason: translate('settings.almintegration.feature.need_at_least_1_binding')
}
]}
form={childProps => <GithubForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGithubConfiguration}
/>

<div className="huge-spacer-top huge-spacer-bottom bordered-top" />
</>
)}

<div className="big-padded">
<CategoryDefinitionsList
category={ALM_INTEGRATION}
component={component}
subCategory={AlmKeys.GitHub}
/>
</div>
</div>
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GitlabForm.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabForm.tsx View File

import * as React from 'react'; import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { GitlabBindingDefinition } from '../../../../types/alm-settings'; import { GitlabBindingDefinition } from '../../../../types/alm-settings';
import { AlmDefinitionFormField } from './AlmDefinitionFormField';
import { AlmBindingDefinitionFormField } from './AlmBindingDefinitionFormField';


export interface GitlabFormProps { export interface GitlabFormProps {
formData: GitlabBindingDefinition; formData: GitlabBindingDefinition;
return ( return (
<> <>
{!hideKeyField && ( {!hideKeyField && (
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help={translate('settings.pr_decoration.form.name.gitlab.help')}
help={translate('settings.almintegration.form.name.gitlab.help')}
id="name.gitlab" id="name.gitlab"
onFieldChange={onFieldChange} onFieldChange={onFieldChange}
propKey="key" propKey="key"
value={formData.key} value={formData.key}
/> />
)} )}
<AlmDefinitionFormField
help={translate('settings.pr_decoration.form.personal_access_token.gitlab.help')}
<AlmBindingDefinitionFormField
help={translate('settings.almintegration.form.personal_access_token.gitlab.help')}
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={onFieldChange} onFieldChange={onFieldChange}

+ 80
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/GitlabTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createGitlabConfiguration, updateGitlabConfiguration } from '../../../../api/alm-settings';
import { AlmKeys, GitlabBindingDefinition } from '../../../../types/alm-settings';
import { ALM_INTEGRATION } from '../AdditionalCategoryKeys';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
import AlmTab from './AlmTab';
import GitlabForm from './GitlabForm';

export interface GitlabTabProps {
branchesEnabled: boolean;
component?: T.Component;
definitions: GitlabBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function GitlabTab(props: GitlabTabProps) {
const { branchesEnabled, component, multipleAlmEnabled, definitions, loading } = props;

return (
<div className="bordered">
{branchesEnabled && (
<>
<AlmTab
alm={AlmKeys.GitLab}
createConfiguration={createGitlabConfiguration}
defaultBinding={{ key: '', personalAccessToken: '' }}
definitions={definitions}
features={[
{
name: translate('settings.almintegration.feature.mr_decoration.title'),
active: definitions.length > 0,
description: translate('settings.almintegration.feature.mr_decoration.description'),
inactiveReason: translate('settings.almintegration.feature.need_at_least_1_binding')
}
]}
form={childProps => <GitlabForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGitlabConfiguration}
/>

<div className="huge-spacer-top huge-spacer-bottom bordered-top" />
</>
)}

<div className="big-padded">
<CategoryDefinitionsList
category={ALM_INTEGRATION}
component={component}
subCategory={AlmKeys.GitLab}
/>
</div>
</div>
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationForm-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionForm-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { mockGithubDefinition } from '../../../../../helpers/mocks/alm-settings'; import { mockGithubDefinition } from '../../../../../helpers/mocks/alm-settings';
import { ALM_KEYS, GithubBindingDefinition } from '../../../../../types/alm-settings';
import AlmPRDecorationForm from '../AlmPRDecorationForm';
import { GithubBindingDefinition } from '../../../../../types/alm-settings';
import AlmBindingDefinitionForm from '../AlmBindingDefinitionForm';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot(); expect(shallowRender()).toMatchSnapshot();
expect(wrapper.instance().canSubmit()).toBe(true); expect(wrapper.instance().canSubmit()).toBe(true);
}); });


function shallowRender(props: Partial<AlmPRDecorationForm<GithubBindingDefinition>['props']> = {}) {
return shallow<AlmPRDecorationForm<GithubBindingDefinition>>(
<AlmPRDecorationForm
alm={ALM_KEYS.GITHUB}
function shallowRender(
props: Partial<AlmBindingDefinitionForm<GithubBindingDefinition>['props']> = {}
) {
return shallow<AlmBindingDefinitionForm<GithubBindingDefinition>>(
<AlmBindingDefinitionForm
bindingDefinition={{ appId: '', key: '', privateKey: '', url: '' }} bindingDefinition={{ appId: '', key: '', privateKey: '', url: '' }}
onCancel={jest.fn()} onCancel={jest.fn()}
onSubmit={jest.fn()} onSubmit={jest.fn()}
{...props}> {...props}>
{() => null} {() => null}
</AlmPRDecorationForm>
</AlmBindingDefinitionForm>
); );
} }

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmDefinitionFormField-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormField-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { AlmSettingsBinding } from '../../../../../types/alm-settings';
import { AlmDefinitionFormField, AlmDefinitionFormFieldProps } from '../AlmDefinitionFormField';
import { AlmBindingDefinition } from '../../../../../types/alm-settings';
import {
AlmBindingDefinitionFormField,
AlmBindingDefinitionFormFieldProps
} from '../AlmBindingDefinitionFormField';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot(); expect(shallowRender()).toMatchSnapshot();
expect(onTextAreaChange).toBeCalled(); expect(onTextAreaChange).toBeCalled();
}); });


function shallowRender(props: Partial<AlmDefinitionFormFieldProps<AlmSettingsBinding>> = {}) {
function shallowRender(
props: Partial<AlmBindingDefinitionFormFieldProps<AlmBindingDefinition>> = {}
) {
return shallow( return shallow(
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="key" id="key"
isTextArea={false} isTextArea={false}
maxLength={40} maxLength={40}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationFormModalRenderer-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormModalRenderer-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import AlmPRDecorationFormModalRenderer, {
AlmPRDecorationFormModalProps
} from '../AlmPRDecorationFormModalRenderer';
import AlmBindingDefinitionFormModalRenderer, {
AlmBindingDefinitionFormModalProps
} from '../AlmBindingDefinitionFormModalRenderer';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender().dive()).toMatchSnapshot(); expect(shallowRender().dive()).toMatchSnapshot();
expect(shallowRender({ help: <span>Help me</span> }).dive()).toMatchSnapshot(); expect(shallowRender({ help: <span>Help me</span> }).dive()).toMatchSnapshot();
}); });


function shallowRender(props: Partial<AlmPRDecorationFormModalProps> = {}) {
function shallowRender(props: Partial<AlmBindingDefinitionFormModalProps> = {}) {
return shallow( return shallow(
<AlmPRDecorationFormModalRenderer
<AlmBindingDefinitionFormModalRenderer
action="create" action="create"
alm={ALM_KEYS.GITHUB}
canSubmit={jest.fn()} canSubmit={jest.fn()}
onCancel={jest.fn()} onCancel={jest.fn()}
onSubmit={jest.fn()} onSubmit={jest.fn()}
{...props}> {...props}>
{() => null} {() => null}
</AlmPRDecorationFormModalRenderer>
</AlmBindingDefinitionFormModalRenderer>
); );
} }

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationFormRenderer-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionFormRenderer-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import { submit } from 'sonar-ui-common/helpers/testUtils'; import { submit } from 'sonar-ui-common/helpers/testUtils';
import AlmPRDecorationFormRenderer, {
AlmPRDecorationFormRendererProps
} from '../AlmPRDecorationFormRenderer';
import AlmBindingDefinitionFormRenderer, {
AlmBindingDefinitionFormRendererProps
} from '../AlmBindingDefinitionFormRenderer';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot(); expect(shallowRender()).toMatchSnapshot();
expect(onSubmit).toBeCalled(); expect(onSubmit).toBeCalled();
}); });


function shallowRender(props: Partial<AlmPRDecorationFormRendererProps> = {}) {
function shallowRender(props: Partial<AlmBindingDefinitionFormRendererProps> = {}) {
return shallow( return shallow(
<AlmPRDecorationFormRenderer
<AlmBindingDefinitionFormRenderer
canSubmit={jest.fn()} canSubmit={jest.fn()}
loading={false} loading={false}
onSubmit={jest.fn()} onSubmit={jest.fn()}
success={false} success={false}
{...props}> {...props}>
{() => null} {() => null}
</AlmPRDecorationFormRenderer>
</AlmBindingDefinitionFormRenderer>
); );
} }

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmPRDecorationTable-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmBindingDefinitionsTable-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import AlmPRDecorationTable, { AlmPRDecorationTableProps } from '../AlmPRDecorationTable';
import { AlmKeys } from '../../../../../types/alm-settings';
import AlmBindingDefinitionsTable, {
AlmBindingDefinitionsTableProps
} from '../AlmBindingDefinitionsTable';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot(); expect(shallowRender()).toMatchSnapshot();
expect( expect(
shallowRender({ shallowRender({
additionalColumnsHeaders: ['additional1', 'additional2'], additionalColumnsHeaders: ['additional1', 'additional2'],
alm: ALM_KEYS.GITHUB,
alm: AlmKeys.GitHub,
definitions: [ definitions: [
{ key: 'definition1', additionalColumns: ['def1-v1', 'def1-v2'] }, { key: 'definition1', additionalColumns: ['def1-v1', 'def1-v2'] },
{ key: 'definition2', additionalColumns: ['def2-v1', 'def2-v2'] } { key: 'definition2', additionalColumns: ['def2-v1', 'def2-v2'] }
] ]
}) })
).toMatchSnapshot('additional columns'); ).toMatchSnapshot('additional columns');
expect(shallowRender({ alm: ALM_KEYS.GITHUB })).toMatchSnapshot('title adjusts for GitLab');
expect(shallowRender({ alm: AlmKeys.GitLab })).toMatchSnapshot('title adjusts for GitLab');
}); });


it('should correctly trigger create, delete, and edit', () => { it('should correctly trigger create, delete, and edit', () => {


const wrapper = shallowRender({ const wrapper = shallowRender({
additionalColumnsHeaders: [], additionalColumnsHeaders: [],
alm: ALM_KEYS.BITBUCKET,
alm: AlmKeys.Bitbucket,
definitions: [{ key: 'defKey', additionalColumns: [] }], definitions: [{ key: 'defKey', additionalColumns: [] }],
onCreate, onCreate,
onDelete, onDelete,
expect(onEdit).toBeCalledWith('defKey'); expect(onEdit).toBeCalledWith('defKey');
}); });


function shallowRender(props: Partial<AlmPRDecorationTableProps> = {}) {
function shallowRender(props: Partial<AlmBindingDefinitionsTableProps> = {}) {
return shallow( return shallow(
<AlmPRDecorationTable
<AlmBindingDefinitionsTable
additionalColumnsHeaders={[]} additionalColumnsHeaders={[]}
alm={ALM_KEYS.AZURE}
alm={AlmKeys.Azure}
definitions={[]} definitions={[]}
onCreate={jest.fn()} onCreate={jest.fn()}
onDelete={jest.fn()} onDelete={jest.fn()}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PullRequestDecoration-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegration-test.tsx View File

countBindedProjects, countBindedProjects,
deleteConfiguration, deleteConfiguration,
getAlmDefinitions getAlmDefinitions
} from '../../../../../api/almSettings';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import PullRequestDecoration from '../PullRequestDecoration';
} from '../../../../../api/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import { AlmIntegration } from '../AlmIntegration';


jest.mock('../../../../../api/almSettings', () => ({
jest.mock('../../../../../api/alm-settings', () => ({
countBindedProjects: jest.fn().mockResolvedValue(0), countBindedProjects: jest.fn().mockResolvedValue(0),
deleteConfiguration: jest.fn().mockResolvedValue(undefined), deleteConfiguration: jest.fn().mockResolvedValue(undefined),
getAlmDefinitions: jest.fn().mockResolvedValue({ github: [] }) getAlmDefinitions: jest.fn().mockResolvedValue({ github: [] })
it('should handle alm selection', async () => { it('should handle alm selection', async () => {
const wrapper = shallowRender(); const wrapper = shallowRender();


wrapper.setState({ currentAlm: ALM_KEYS.AZURE });
wrapper.setState({ currentAlm: AlmKeys.Azure });


wrapper.instance().handleSelectAlm(ALM_KEYS.GITHUB);
wrapper.instance().handleSelectAlm(AlmKeys.GitHub);


await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(wrapper.state().currentAlm).toBe(ALM_KEYS.GITHUB);
expect(wrapper.state().currentAlm).toBe(AlmKeys.GitHub);
}); });


it('should handle delete', async () => { it('should handle delete', async () => {
}); });


function shallowRender() { function shallowRender() {
return shallow<PullRequestDecoration>(<PullRequestDecoration />);
return shallow<AlmIntegration>(<AlmIntegration appState={{ branchesEnabled: true }} />);
} }

+ 39
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationFeatureBox-test.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { shallow } from 'enzyme';
import * as React from 'react';
import AlmIntegrationFeatureBox, {
AlmIntegrationFeatureBoxProps
} from '../AlmIntegrationFeatureBox';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ active: false })).toMatchSnapshot('inactive');
expect(shallowRender({ active: false, inactiveReason: "Bar is foo'd" })).toMatchSnapshot(
'inactive, with reason'
);
});

function shallowRender(props: Partial<AlmIntegrationFeatureBoxProps> = {}) {
return shallow<AlmIntegrationFeatureBoxProps>(
<AlmIntegrationFeatureBox active={true} description="Foo bar..." name="Foo" {...props} />
);
}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/PRDecorationTabs-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmIntegrationRenderer-test.tsx View File

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import { PRDecorationTabs, PRDecorationTabsProps } from '../PRDecorationTabs';
import { AlmKeys } from '../../../../../types/alm-settings';
import AlmIntegrationRenderer, { AlmIntegrationRendererProps } from '../AlmIntegrationRenderer';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender({ loading: true })).toMatchSnapshot();
expect(shallowRender({ definitionKeyForDeletion: 'keyToDelete' })).toMatchSnapshot();
expect(shallowRender({ currentAlm: ALM_KEYS.AZURE })).toMatchSnapshot();
expect(shallowRender({ currentAlm: ALM_KEYS.BITBUCKET })).toMatchSnapshot();
expect(shallowRender({ currentAlm: ALM_KEYS.GITLAB })).toMatchSnapshot();
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ loading: true })).toMatchSnapshot('loading');
expect(shallowRender({ definitionKeyForDeletion: 'keyToDelete' })).toMatchSnapshot(
'delete modal'
);
expect(shallowRender({ currentAlm: AlmKeys.Azure })).toMatchSnapshot('azure');
expect(shallowRender({ currentAlm: AlmKeys.Bitbucket })).toMatchSnapshot('bitbucket');
expect(shallowRender({ currentAlm: AlmKeys.GitLab })).toMatchSnapshot('gitlab');
}); });


function shallowRender(props: Partial<PRDecorationTabsProps> = {}) {
function shallowRender(props: Partial<AlmIntegrationRendererProps> = {}) {
return shallow( return shallow(
<PRDecorationTabs
appState={{ multipleAlmEnabled: false }}
currentAlm={ALM_KEYS.GITHUB}
<AlmIntegrationRenderer
branchesEnabled={true}
currentAlm={AlmKeys.GitHub}
definitions={{ azure: [], bitbucket: [], github: [], gitlab: [] }} definitions={{ azure: [], bitbucket: [], github: [], gitlab: [] }}
loading={false} loading={false}
multipleAlmEnabled={false}
onCancel={jest.fn()} onCancel={jest.fn()}
onConfirmDelete={jest.fn()} onConfirmDelete={jest.fn()}
onDelete={jest.fn()} onDelete={jest.fn()}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmTab-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTab-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { mockAzureDefinition } from '../../../../../helpers/mocks/alm-settings'; import { mockAzureDefinition } from '../../../../../helpers/mocks/alm-settings';
import { ALM_KEYS, AzureBindingDefinition } from '../../../../../types/alm-settings';
import { AlmKeys, AzureBindingDefinition } from '../../../../../types/alm-settings';
import AlmTab from '../AlmTab'; import AlmTab from '../AlmTab';


const DEFAULT_BINDING = { const DEFAULT_BINDING = {
function shallowRender(props: Partial<AlmTab<AzureBindingDefinition>['props']> = {}) { function shallowRender(props: Partial<AlmTab<AzureBindingDefinition>['props']> = {}) {
return shallow<AlmTab<AzureBindingDefinition>>( return shallow<AlmTab<AzureBindingDefinition>>(
<AlmTab <AlmTab
alm={ALM_KEYS.AZURE}
alm={AlmKeys.Azure}
createConfiguration={jest.fn()} createConfiguration={jest.fn()}
defaultBinding={DEFAULT_BINDING} defaultBinding={DEFAULT_BINDING}
definitions={[mockAzureDefinition()]} definitions={[mockAzureDefinition()]}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AlmTabRenderer-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AlmTabRenderer-test.tsx View File

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockGithubDefinition } from '../../../../../helpers/mocks/alm-settings'; import { mockGithubDefinition } from '../../../../../helpers/mocks/alm-settings';
import { ALM_KEYS, GithubBindingDefinition } from '../../../../../types/alm-settings';
import { AlmKeys, GithubBindingDefinition } from '../../../../../types/alm-settings';
import AlmTabRenderer, { AlmTabRendererProps } from '../AlmTabRenderer'; import AlmTabRenderer, { AlmTabRendererProps } from '../AlmTabRenderer';


it('should render correctly for multi-ALM binding', () => { it('should render correctly for multi-ALM binding', () => {
expect(shallowRender({ editedDefinition: mockGithubDefinition() })).toMatchSnapshot( expect(shallowRender({ editedDefinition: mockGithubDefinition() })).toMatchSnapshot(
'editing a definition' 'editing a definition'
); );
expect(
shallowRender({
features: [
{
active: true,
name: 'Foo',
description: 'Bar'
},
{
active: false,
name: 'Baz',
description: 'Bim'
}
]
})
).toMatchSnapshot('with features');
}); });


it('should render correctly for single-ALM binding', () => { it('should render correctly for single-ALM binding', () => {
<AlmTabRenderer <AlmTabRenderer
additionalColumnsHeaders={['url', 'app_id']} additionalColumnsHeaders={['url', 'app_id']}
additionalColumnsKeys={['url', 'appId']} additionalColumnsKeys={['url', 'appId']}
alm={ALM_KEYS.GITHUB}
alm={AlmKeys.GitHub}
defaultBinding={mockGithubDefinition()} defaultBinding={mockGithubDefinition()}
definitions={[mockGithubDefinition()]} definitions={[mockGithubDefinition()]}
form={jest.fn()} form={jest.fn()}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureForm-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureForm-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/AzureTab-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/AzureTab-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/BitbucketForm-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketForm-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/BitbucketTab-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/BitbucketTab-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/DeleteModal-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/DeleteModal-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubForm-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubForm-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GithubTab-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GithubTab-test.tsx View File

import GithubTab, { GithubTabProps } from '../GithubTab'; import GithubTab, { GithubTabProps } from '../GithubTab';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender()).toMatchSnapshot('with branch support');
expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot('without branch support');
}); });


function shallowRender(props: Partial<GithubTabProps> = {}) { function shallowRender(props: Partial<GithubTabProps> = {}) {
return shallow( return shallow(
<GithubTab <GithubTab
branchesEnabled={true}
definitions={[mockGithubDefinition()]} definitions={[mockGithubDefinition()]}
loading={false} loading={false}
multipleAlmEnabled={true} multipleAlmEnabled={true}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GitlabForm-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabForm-test.tsx View File


server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/GitlabTab-test.tsx → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/GitlabTab-test.tsx View File

import GitlabTab, { GitlabTabProps } from '../GitlabTab'; import GitlabTab, { GitlabTabProps } from '../GitlabTab';


it('should render correctly', () => { it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender()).toMatchSnapshot('with branch support');
expect(shallowRender({ branchesEnabled: false })).toMatchSnapshot('without branch support');
}); });


function shallowRender(props: Partial<GitlabTabProps> = {}) { function shallowRender(props: Partial<GitlabTabProps> = {}) {
return shallow( return shallow(
<GitlabTab <GitlabTab
branchesEnabled={true}
definitions={[mockGitlabDefinition()]} definitions={[mockGitlabDefinition()]}
loading={false} loading={false}
multipleAlmEnabled={true} multipleAlmEnabled={true}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationForm-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionForm-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<AlmPRDecorationFormRenderer
<AlmBindingDefinitionFormRenderer
canSubmit={[Function]} canSubmit={[Function]}
loading={false} loading={false}
onCancel={[Function]} onCancel={[Function]}

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmDefinitionFormField-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormField-test.tsx.snap View File

className="display-flex-center" className="display-flex-center"
htmlFor="key" htmlFor="key"
> >
settings.pr_decoration.form.key
settings.almintegration.form.key
<em <em
className="mandatory spacer-right" className="mandatory spacer-right"
> >
className="display-flex-center" className="display-flex-center"
htmlFor="key" htmlFor="key"
> >
settings.pr_decoration.form.key
settings.almintegration.form.key
<em <em
className="mandatory spacer-right" className="mandatory spacer-right"
> >
className="display-flex-center" className="display-flex-center"
htmlFor="key" htmlFor="key"
> >
settings.pr_decoration.form.key
settings.almintegration.form.key
<em <em
className="mandatory spacer-right" className="mandatory spacer-right"
> >

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/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormModalRenderer-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Modal <Modal
contentLabel="settings.pr_decoration.form.header.create"
contentLabel="settings.almintegration.form.header.create"
onRequestClose={[MockFunction]} onRequestClose={[MockFunction]}
size="medium" size="medium"
> >
className="modal-head" className="modal-head"
> >
<h2> <h2>
settings.pr_decoration.form.header.create
settings.almintegration.form.header.create
</h2> </h2>
</div> </div>
<div <div
className="modal-body modal-container" className="modal-body modal-container"
> >
<Component />
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
</div>
</div>
</div> </div>
<div <div
className="modal-foot" className="modal-foot"
<SubmitButton <SubmitButton
disabled={true} disabled={true}
> >
settings.pr_decoration.form.save
settings.almintegration.form.save
</SubmitButton> </SubmitButton>
<ResetButtonLink <ResetButtonLink
onClick={[Function]} onClick={[Function]}


exports[`should render correctly 2`] = ` exports[`should render correctly 2`] = `
<Modal <Modal
contentLabel="settings.pr_decoration.form.header.create"
contentLabel="settings.almintegration.form.header.create"
onRequestClose={[MockFunction]} onRequestClose={[MockFunction]}
size="medium" size="medium"
> >
className="modal-head" className="modal-head"
> >
<h2> <h2>
settings.pr_decoration.form.header.create
settings.almintegration.form.header.create
</h2> </h2>
</div> </div>
<div <div
className="modal-body modal-container" className="modal-body modal-container"
> >
<Alert
className="big-spacer-bottom"
variant="info"
<div
className="display-flex-start"
> >
<span>
Help me
</span>
</Alert>
<Component />
<div
className="flex-1"
>
<Component />
</div>
<Alert
className="huge-spacer-left flex-1"
variant="info"
>
<span>
Help me
</span>
</Alert>
</div>
</div> </div>
<div <div
className="modal-foot" className="modal-foot"
<SubmitButton <SubmitButton
disabled={true} disabled={true}
> >
settings.pr_decoration.form.save
settings.almintegration.form.save
</SubmitButton> </SubmitButton>
<ResetButtonLink <ResetButtonLink
onClick={[Function]} onClick={[Function]}

+ 161
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionFormRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.almintegration.form.save
</SubmitButton>
</div>
</div>
</div>
</form>
`;

exports[`should render correctly 2`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.almintegration.form.save
</SubmitButton>
<ResetButtonLink
className="spacer-left"
onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
</div>
</div>
</div>
</form>
`;

exports[`should render correctly 3`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.almintegration.form.save
</SubmitButton>
<Button
className="button-red spacer-left"
disabled={false}
onClick={[MockFunction]}
>
delete
</Button>
</div>
</div>
</div>
</form>
`;

exports[`should render correctly 4`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.almintegration.form.save
</SubmitButton>
<span
className="text-success spacer-left"
>
<AlertSuccessIcon
className="spacer-right"
/>
settings.state.saved
</span>
</div>
</div>
</div>
</form>
`;

exports[`should render correctly 5`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<div
className="display-flex-start"
>
<div
className="flex-1"
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.almintegration.form.save
</SubmitButton>
<DeferredSpinner
className="spacer-left"
timeout={100}
/>
</div>
</div>
</div>
</form>
`;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationTable-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmBindingDefinitionsTable-test.tsx.snap View File

<div <div
className="spacer-top big-spacer-bottom display-flex-space-between" className="spacer-top big-spacer-bottom display-flex-space-between"
> >
<h4
className="display-inline"
<h2
className="settings-sub-category-name"
> >
settings.pr_decoration.table.title
</h4>
settings.almintegration.table.title
</h2>
<Button <Button
data-test="settings__alm-create" data-test="settings__alm-create"
onClick={[MockFunction]} onClick={[MockFunction]}
> >
settings.pr_decoration.table.create
settings.almintegration.table.create
</Button> </Button>
</div> </div>
<table <table
<thead> <thead>
<tr> <tr>
<th> <th>
settings.pr_decoration.table.column.name
settings.almintegration.table.column.name
</th> </th>
<th <th
className="action-small text-center" className="action-small text-center"
> >
settings.pr_decoration.table.column.edit
settings.almintegration.table.column.edit
</th> </th>
<th <th
className="action text-center" className="action text-center"
> >
settings.pr_decoration.table.column.delete
settings.almintegration.table.column.delete
</th> </th>
</tr> </tr>
</thead> </thead>
<td <td
colSpan={3} colSpan={3}
> >
settings.pr_decoration.table.empty.azure
settings.almintegration.table.empty.azure
</td> </td>
</tr> </tr>
</tbody> </tbody>
<div <div
className="spacer-top big-spacer-bottom display-flex-space-between" className="spacer-top big-spacer-bottom display-flex-space-between"
> >
<h4
className="display-inline"
<h2
className="settings-sub-category-name"
> >
settings.pr_decoration.table.title
</h4>
settings.almintegration.table.title
</h2>
<Button <Button
data-test="settings__alm-create" data-test="settings__alm-create"
onClick={[MockFunction]} onClick={[MockFunction]}
> >
settings.pr_decoration.table.create
settings.almintegration.table.create
</Button> </Button>
</div> </div>
<table <table
<thead> <thead>
<tr> <tr>
<th> <th>
settings.pr_decoration.table.column.name
settings.almintegration.table.column.name
</th> </th>
<th <th
key="additional1" key="additional1"
<th <th
className="action-small text-center" className="action-small text-center"
> >
settings.pr_decoration.table.column.edit
settings.almintegration.table.column.edit
</th> </th>
<th <th
className="action text-center" className="action text-center"
> >
settings.pr_decoration.table.column.delete
settings.almintegration.table.column.delete
</th> </th>
</tr> </tr>
</thead> </thead>
<div <div
className="spacer-top big-spacer-bottom display-flex-space-between" className="spacer-top big-spacer-bottom display-flex-space-between"
> >
<h4
className="display-inline"
<h2
className="settings-sub-category-name"
> >
settings.pr_decoration.table.title
</h4>
settings.almintegration.table.title
</h2>
<Button <Button
data-test="settings__alm-create" data-test="settings__alm-create"
onClick={[MockFunction]} onClick={[MockFunction]}
> >
settings.pr_decoration.table.create
settings.almintegration.table.create
</Button> </Button>
</div> </div>
<table <table
<thead> <thead>
<tr> <tr>
<th> <th>
settings.pr_decoration.table.column.name
settings.almintegration.table.column.name
</th> </th>
<th <th
className="action-small text-center" className="action-small text-center"
> >
settings.pr_decoration.table.column.edit
settings.almintegration.table.column.edit
</th> </th>
<th <th
className="action text-center" className="action text-center"
> >
settings.pr_decoration.table.column.delete
settings.almintegration.table.column.delete
</th> </th>
</tr> </tr>
</thead> </thead>
<td <td
colSpan={3} colSpan={3}
> >
settings.pr_decoration.table.empty.github
settings.almintegration.table.empty.gitlab
</td> </td>
</tr> </tr>
</tbody> </tbody>

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/almIntegration/__tests__/__snapshots__/AlmIntegration-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Connect(withAppState(PRDecorationTabs))
<AlmIntegrationRenderer
branchesEnabled={true}
currentAlm="github" currentAlm="github"
definitions={ definitions={
Object { Object {
} }
} }
loading={true} loading={true}
multipleAlmEnabled={false}
onCancel={[Function]} onCancel={[Function]}
onConfirmDelete={[Function]} onConfirmDelete={[Function]}
onDelete={[Function]} onDelete={[Function]}

+ 97
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmIntegrationFeatureBox-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="boxed-group-inner display-flex-start width-30 spacer-right spacer-bottom bordered"
>
<CheckIcon
className="little-spacer-top spacer-right"
fill="#00aa00"
/>
<div
className="display-flex-column abs-height-100"
>
<h4>
Foo
</h4>
<div
className="spacer-top flex-1"
>
Foo bar...
</div>
<div
className="spacer-top"
>
<em
className="text-success"
>
settings.almintegration.feature.enabled
</em>
</div>
</div>
</div>
`;

exports[`should render correctly: inactive 1`] = `
<div
className="boxed-group-inner display-flex-start width-30 spacer-right spacer-bottom bordered bg-muted"
>
<ClearIcon
className="little-spacer-top spacer-right"
fill="#999"
/>
<div
className="display-flex-column abs-height-100"
>
<h4>
Foo
</h4>
<div
className="spacer-top flex-1"
>
Foo bar...
</div>
<div
className="spacer-top"
>
<em
className="text-muted"
>
settings.almintegration.feature.disabled
</em>
</div>
</div>
</div>
`;

exports[`should render correctly: inactive, with reason 1`] = `
<div
className="boxed-group-inner display-flex-start width-30 spacer-right spacer-bottom bordered bg-muted"
>
<ClearIcon
className="little-spacer-top spacer-right"
fill="#999"
/>
<div
className="display-flex-column abs-height-100"
>
<h4>
Foo
</h4>
<div
className="spacer-top flex-1"
>
Foo bar...
</div>
<div
className="spacer-top"
>
<em
className="text-muted"
>
Bar is foo'd
</em>
</div>
</div>
</div>
`;

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/almIntegration/__tests__/__snapshots__/AlmIntegrationRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = `
exports[`should render correctly: azure 1`] = `
<Fragment> <Fragment>
<header <header
className="page-header" className="page-header"
<h1 <h1
className="page-title" className="page-title"
> >
settings.pr_decoration.title
settings.almintegration.title
</h1> </h1>
</header> </header>
<div <div
className="markdown small spacer-top big-spacer-bottom" className="markdown small spacer-top big-spacer-bottom"
> >
settings.pr_decoration.description
settings.almintegration.description
</div> </div>
<BoxedTabs <BoxedTabs
onSelect={[MockFunction]} onSelect={[MockFunction]}
selected="github"
selected="azure"
tabs={ tabs={
Array [ Array [
Object { Object {
/> />
Bitbucket Server Bitbucket Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "azure", "key": "azure",
/> />
Azure DevOps Server Azure DevOps Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "gitlab", "key": "gitlab",
] ]
} }
/> />
<div
className="boxed-group boxed-group-inner"
>
<GithubTab
definitions={Array []}
loading={true}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</div>
<AzureTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</Fragment> </Fragment>
`; `;


exports[`should render correctly 2`] = `
exports[`should render correctly: bitbucket 1`] = `
<Fragment> <Fragment>
<header <header
className="page-header" className="page-header"
<h1 <h1
className="page-title" className="page-title"
> >
settings.pr_decoration.title
settings.almintegration.title
</h1> </h1>
</header> </header>
<div <div
className="markdown small spacer-top big-spacer-bottom" className="markdown small spacer-top big-spacer-bottom"
> >
settings.pr_decoration.description
settings.almintegration.description
</div> </div>
<BoxedTabs <BoxedTabs
onSelect={[MockFunction]} onSelect={[MockFunction]}
selected="github"
selected="bitbucket"
tabs={ tabs={
Array [ Array [
Object { Object {
/> />
Bitbucket Server Bitbucket Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "azure", "key": "azure",
/> />
Azure DevOps Server Azure DevOps Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "gitlab", "key": "gitlab",
] ]
} }
/> />
<div
className="boxed-group boxed-group-inner"
>
<GithubTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</div>
<DeleteModal
id="keyToDelete"
onCancel={[MockFunction]}
<BitbucketTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]} onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/> />
</Fragment> </Fragment>
`; `;


exports[`should render correctly 3`] = `
exports[`should render correctly: default 1`] = `
<Fragment> <Fragment>
<header <header
className="page-header" className="page-header"
<h1 <h1
className="page-title" className="page-title"
> >
settings.pr_decoration.title
settings.almintegration.title
</h1> </h1>
</header> </header>
<div <div
className="markdown small spacer-top big-spacer-bottom" className="markdown small spacer-top big-spacer-bottom"
> >
settings.pr_decoration.description
settings.almintegration.description
</div> </div>
<BoxedTabs <BoxedTabs
onSelect={[MockFunction]} onSelect={[MockFunction]}
selected="azure"
selected="github"
tabs={ tabs={
Array [ Array [
Object { Object {
/> />
Bitbucket Server Bitbucket Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "azure", "key": "azure",
/> />
Azure DevOps Server Azure DevOps Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "gitlab", "key": "gitlab",
] ]
} }
/> />
<div
className="boxed-group boxed-group-inner"
>
<AzureTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</div>
<GithubTab
branchesEnabled={true}
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</Fragment> </Fragment>
`; `;


exports[`should render correctly 4`] = `
exports[`should render correctly: delete modal 1`] = `
<Fragment> <Fragment>
<header <header
className="page-header" className="page-header"
<h1 <h1
className="page-title" className="page-title"
> >
settings.pr_decoration.title
settings.almintegration.title
</h1> </h1>
</header> </header>
<div <div
className="markdown small spacer-top big-spacer-bottom" className="markdown small spacer-top big-spacer-bottom"
> >
settings.pr_decoration.description
settings.almintegration.description
</div> </div>
<BoxedTabs <BoxedTabs
onSelect={[MockFunction]} onSelect={[MockFunction]}
selected="bitbucket"
selected="github"
tabs={ tabs={
Array [ Array [
Object { Object {
/> />
Bitbucket Server Bitbucket Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "azure", "key": "azure",
/> />
Azure DevOps Server Azure DevOps Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "gitlab", "key": "gitlab",
] ]
} }
/> />
<div
className="boxed-group boxed-group-inner"
>
<BitbucketTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</div>
<GithubTab
branchesEnabled={true}
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
<DeleteModal
id="keyToDelete"
onCancel={[MockFunction]}
onDelete={[MockFunction]}
/>
</Fragment> </Fragment>
`; `;


exports[`should render correctly 5`] = `
exports[`should render correctly: gitlab 1`] = `
<Fragment> <Fragment>
<header <header
className="page-header" className="page-header"
<h1 <h1
className="page-title" className="page-title"
> >
settings.pr_decoration.title
settings.almintegration.title
</h1> </h1>
</header> </header>
<div <div
className="markdown small spacer-top big-spacer-bottom" className="markdown small spacer-top big-spacer-bottom"
> >
settings.pr_decoration.description
settings.almintegration.description
</div> </div>
<BoxedTabs <BoxedTabs
onSelect={[MockFunction]} onSelect={[MockFunction]}
/> />
Bitbucket Server Bitbucket Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "azure", "key": "azure",
/> />
Azure DevOps Server Azure DevOps Server
</React.Fragment>, </React.Fragment>,
"requiresBranchesEnabled": true,
}, },
Object { Object {
"key": "gitlab", "key": "gitlab",
] ]
} }
/> />
<GitlabTab
branchesEnabled={true}
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</Fragment>
`;

exports[`should render correctly: loading 1`] = `
<Fragment>
<header
className="page-header"
>
<h1
className="page-title"
>
settings.almintegration.title
</h1>
</header>
<div <div
className="boxed-group boxed-group-inner"
className="markdown small spacer-top big-spacer-bottom"
> >
<GitlabTab
definitions={Array []}
loading={false}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
settings.almintegration.description
</div> </div>
<BoxedTabs
onSelect={[MockFunction]}
selected="github"
tabs={
Array [
Object {
"key": "github",
"label": <React.Fragment>
<img
alt="github"
className="spacer-right"
height={16}
src="/images/alm/github.svg"
/>
GitHub
</React.Fragment>,
},
Object {
"key": "bitbucket",
"label": <React.Fragment>
<img
alt="bitbucket"
className="spacer-right"
height={16}
src="/images/alm/bitbucket.svg"
/>
Bitbucket Server
</React.Fragment>,
"requiresBranchesEnabled": true,
},
Object {
"key": "azure",
"label": <React.Fragment>
<img
alt="azure"
className="spacer-right"
height={16}
src="/images/alm/azure.svg"
/>
Azure DevOps Server
</React.Fragment>,
"requiresBranchesEnabled": true,
},
Object {
"key": "gitlab",
"label": <React.Fragment>
<img
alt="gitlab"
className="spacer-right"
height={16}
src="/images/alm/gitlab.svg"
/>
GitLab
</React.Fragment>,
},
]
}
/>
<GithubTab
branchesEnabled={true}
definitions={Array []}
loading={true}
multipleAlmEnabled={false}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
/>
</Fragment> </Fragment>
`; `;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmTab-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTab-test.tsx.snap View File


+ 334
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly for multi-ALM binding: editing a definition 1`] = `
<div
className="big-padded"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<AlmBindingDefinitionsTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
<AlmBindingDefinitionForm
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.almintegration.github.info"
id="settings.almintegration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
showInModal={true}
>
<Component />
</AlmBindingDefinitionForm>
</DeferredSpinner>
</div>
`;

exports[`should render correctly for multi-ALM binding: loaded 1`] = `
<div
className="big-padded"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<AlmBindingDefinitionsTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
</div>
`;

exports[`should render correctly for multi-ALM binding: loading 1`] = `
<div
className="big-padded"
>
<DeferredSpinner
loading={true}
timeout={100}
>
<AlmBindingDefinitionsTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
</div>
`;

exports[`should render correctly for multi-ALM binding: with features 1`] = `
<div
className="big-padded"
>
<DeferredSpinner
loading={false}
timeout={100}
>
<AlmBindingDefinitionsTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
<div
className="big-spacer-top big-padded-top bordered-top"
>
<h3
className="big-spacer-bottom"
>
settings.almintegration.features
</h3>
<div
className="display-flex-wrap"
>
<AlmIntegrationFeatureBox
active={true}
description="Bar"
key="0"
name="Foo"
/>
<AlmIntegrationFeatureBox
active={false}
description="Bim"
key="1"
name="Baz"
/>
</div>
</div>
</div>
`;

exports[`should render correctly for single-ALM binding 1`] = `
<div
className="big-padded"
>
<AlmBindingDefinitionForm
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.almintegration.github.info"
id="settings.almintegration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={true}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmBindingDefinitionForm>
</div>
`;

exports[`should render correctly for single-ALM binding 2`] = `
<div
className="big-padded"
>
<AlmBindingDefinitionForm
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.almintegration.github.info"
id="settings.almintegration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={false}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmBindingDefinitionForm>
</div>
`;

exports[`should render correctly for single-ALM binding 3`] = `
<div
className="big-padded"
>
<AlmBindingDefinitionForm
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.almintegration.github.info"
id="settings.almintegration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={false}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmBindingDefinitionForm>
</div>
`;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureForm-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.azure.help"
help="settings.almintegration.form.name.azure.help"
id="name.azure" id="name.azure"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="" value=""
/> />
<AlmDefinitionFormField
help="settings.pr_decoration.form.personal_access_token.azure.help"
<AlmBindingDefinitionFormField
help="settings.almintegration.form.personal_access_token.azure.help"
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}


exports[`should render correctly 2`] = ` exports[`should render correctly 2`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.azure.help"
help="settings.almintegration.form.name.azure.help"
id="name.azure" id="name.azure"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="key" value="key"
/> />
<AlmDefinitionFormField
help="settings.pr_decoration.form.personal_access_token.azure.help"
<AlmBindingDefinitionFormField
help="settings.almintegration.form.personal_access_token.azure.help"
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}

+ 42
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="bordered"
>
<AlmTab
alm="azure"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
}
}
definitions={
Array [
Object {
"key": "key",
"personalAccessToken": "asdf1234",
},
]
}
features={
Array [
Object {
"active": true,
"description": "settings.almintegration.feature.pr_decoration.description",
"inactiveReason": "settings.almintegration.feature.need_at_least_1_binding",
"name": "settings.almintegration.feature.pr_decoration.title",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
</div>
`;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketForm-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.bitbucket.help"
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket" id="name.bitbucket"
maxLength={100} maxLength={100}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="" value=""
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<FormattedMessage <FormattedMessage
defaultMessage="settings.pr_decoration.form.url.bitbucket.help"
id="settings.pr_decoration.form.url.bitbucket.help"
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={ values={
Object { Object {
"example": "https://bitbucket-server.your-company.com", "example": "https://bitbucket-server.your-company.com",
propKey="url" propKey="url"
value="" value=""
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}


exports[`should render correctly 2`] = ` exports[`should render correctly 2`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.bitbucket.help"
help="settings.almintegration.form.name.bitbucket.help"
id="name.bitbucket" id="name.bitbucket"
maxLength={100} maxLength={100}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="key" value="key"
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<FormattedMessage <FormattedMessage
defaultMessage="settings.pr_decoration.form.url.bitbucket.help"
id="settings.pr_decoration.form.url.bitbucket.help"
defaultMessage="settings.almintegration.form.url.bitbucket.help"
id="settings.almintegration.form.url.bitbucket.help"
values={ values={
Object { Object {
"example": "https://bitbucket-server.your-company.com", "example": "https://bitbucket-server.your-company.com",
propKey="url" propKey="url"
value="http://bbs.enterprise.com" value="http://bbs.enterprise.com"
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}

+ 112
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="bordered"
>
<AlmTab
additionalColumnsHeaders={
Array [
"settings.almintegration.table.column.bitbucket.url",
]
}
additionalColumnsKeys={
Array [
"url",
]
}
additionalTableInfo={
<Alert
className="big-spacer-bottom width-50"
variant="info"
>
<FormattedMessage
defaultMessage="settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances"
id="settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances"
values={
Object {
"feature": <em>
settings.almintegration.feature.alm_repo_import.title
</em>,
}
}
/>
</Alert>
}
alm="bitbucket"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
"url": "",
}
}
definitions={
Array [
Object {
"key": "key",
"personalAccessToken": "asdf1234",
"url": "http://bbs.enterprise.com",
},
]
}
features={
Array [
Object {
"active": true,
"description": "settings.almintegration.feature.pr_decoration.description",
"inactiveReason": "settings.almintegration.feature.need_at_least_1_binding",
"name": "settings.almintegration.feature.pr_decoration.title",
},
Object {
"active": true,
"description": "settings.almintegration.feature.alm_repo_import.description",
"inactiveReason": "onboarding.create_project.too_many_bbs_instances_X.1",
"name": "settings.almintegration.feature.alm_repo_import.title",
},
]
}
form={[Function]}
help={
<React.Fragment>
<h3>
onboarding.create_project.pat_help.title
</h3>
<p
className="big-spacer-top"
>
settings.almintegration.bitbucket.help_1
</p>
<ul
className="big-spacer-top list-styled"
>
<li>
settings.almintegration.bitbucket.help_2
</li>
<li>
settings.almintegration.bitbucket.help_3
</li>
</ul>
<p
className="big-spacer-top big-spacer-bottom"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>
</p>
</React.Fragment>
}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
</div>
`;

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/almIntegration/__tests__/__snapshots__/DeleteModal-test.tsx.snap View File

<ConfirmModal <ConfirmModal
confirmButtonText="delete" confirmButtonText="delete"
confirmData="1" confirmData="1"
header="settings.pr_decoration.delete.header"
header="settings.almintegration.delete.header"
isDestructive={true} isDestructive={true}
onClose={[MockFunction]} onClose={[MockFunction]}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
className="spacer-bottom" className="spacer-bottom"
> >
<FormattedMessage <FormattedMessage
defaultMessage="settings.pr_decoration.delete.message"
id="settings.pr_decoration.delete.message"
defaultMessage="settings.almintegration.delete.message"
id="settings.almintegration.delete.message"
values={ values={
Object { Object {
"id": <b> "id": <b>
/> />
</p> </p>
<p> <p>
settings.pr_decoration.delete.info.4
settings.almintegration.delete.info.4
</p> </p>
</ConfirmModal> </ConfirmModal>
<ConfirmModal <ConfirmModal
confirmButtonText="delete" confirmButtonText="delete"
confirmData="1" confirmData="1"
header="settings.pr_decoration.delete.header"
header="settings.almintegration.delete.header"
isDestructive={true} isDestructive={true}
onClose={[MockFunction]} onClose={[MockFunction]}
onConfirm={[MockFunction]} onConfirm={[MockFunction]}
className="spacer-bottom" className="spacer-bottom"
> >
<FormattedMessage <FormattedMessage
defaultMessage="settings.pr_decoration.delete.message"
id="settings.pr_decoration.delete.message"
defaultMessage="settings.almintegration.delete.message"
id="settings.almintegration.delete.message"
values={ values={
Object { Object {
"id": <b> "id": <b>
/> />
</p> </p>
<p> <p>
settings.pr_decoration.delete.no_info
settings.almintegration.delete.no_info
</p> </p>
</ConfirmModal> </ConfirmModal>
`; `;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubForm-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubForm-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.github.help"
help="settings.almintegration.form.name.github.help"
id="name.github" id="name.github"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="" value=""
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<React.Fragment> <React.Fragment>
settings.pr_decoration.form.url.github.help1
settings.almintegration.form.url.github.help1
<br /> <br />
<em> <em>
https://github.company.com/api/v3 https://github.company.com/api/v3
</em> </em>
<br /> <br />
<br /> <br />
settings.pr_decoration.form.url.github.help2
settings.almintegration.form.url.github.help2
<br /> <br />
<em> <em>
https://api.github.com/ https://api.github.com/
propKey="url" propKey="url"
value="" value=""
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="app_id" id="app_id"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="appId" propKey="appId"
value="" value=""
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="private_key" id="private_key"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}


exports[`should render correctly 2`] = ` exports[`should render correctly 2`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.github.help"
help="settings.almintegration.form.name.github.help"
id="name.github" id="name.github"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="key" value="key"
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
help={ help={
<React.Fragment> <React.Fragment>
settings.pr_decoration.form.url.github.help1
settings.almintegration.form.url.github.help1
<br /> <br />
<em> <em>
https://github.company.com/api/v3 https://github.company.com/api/v3
</em> </em>
<br /> <br />
<br /> <br />
settings.pr_decoration.form.url.github.help2
settings.almintegration.form.url.github.help2
<br /> <br />
<em> <em>
https://api.github.com/ https://api.github.com/
propKey="url" propKey="url"
value="http://github.enterprise.com" value="http://github.enterprise.com"
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="app_id" id="app_id"
maxLength={80} maxLength={80}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="appId" propKey="appId"
value="123456" value="123456"
/> />
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
id="private_key" id="private_key"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}

+ 84
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GithubTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: with branch support 1`] = `
<div
className="bordered"
>
<AlmTab
additionalColumnsHeaders={
Array [
"settings.almintegration.table.column.github.url",
"settings.almintegration.table.column.app_id",
]
}
additionalColumnsKeys={
Array [
"appId",
"url",
]
}
alm="github"
createConfiguration={[Function]}
defaultBinding={
Object {
"appId": "",
"key": "",
"privateKey": "",
"url": "",
}
}
definitions={
Array [
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
},
]
}
features={
Array [
Object {
"active": true,
"description": "settings.almintegration.feature.pr_decoration.description",
"inactiveReason": "settings.almintegration.feature.need_at_least_1_binding",
"name": "settings.almintegration.feature.pr_decoration.title",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
<div
className="huge-spacer-top huge-spacer-bottom bordered-top"
/>
<div
className="big-padded"
>
<Connect(SubCategoryDefinitionsList)
category="almintegration"
subCategory="github"
/>
</div>
</div>
`;

exports[`should render correctly: without branch support 1`] = `
<div
className="bordered"
>
<div
className="big-padded"
>
<Connect(SubCategoryDefinitionsList)
category="almintegration"
subCategory="github"
/>
</div>
</div>
`;

server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GitlabForm-test.tsx.snap → server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabForm-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.gitlab.help"
help="settings.almintegration.form.name.gitlab.help"
id="name.gitlab" id="name.gitlab"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="" value=""
/> />
<AlmDefinitionFormField
help="settings.pr_decoration.form.personal_access_token.gitlab.help"
<AlmBindingDefinitionFormField
help="settings.almintegration.form.personal_access_token.gitlab.help"
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}


exports[`should render correctly 2`] = ` exports[`should render correctly 2`] = `
<Fragment> <Fragment>
<AlmDefinitionFormField
<AlmBindingDefinitionFormField
autoFocus={true} autoFocus={true}
help="settings.pr_decoration.form.name.gitlab.help"
help="settings.almintegration.form.name.gitlab.help"
id="name.gitlab" id="name.gitlab"
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}
propKey="key" propKey="key"
value="foo" value="foo"
/> />
<AlmDefinitionFormField
help="settings.pr_decoration.form.personal_access_token.gitlab.help"
<AlmBindingDefinitionFormField
help="settings.almintegration.form.personal_access_token.gitlab.help"
id="personal_access_token" id="personal_access_token"
isTextArea={true} isTextArea={true}
onFieldChange={[MockFunction]} onFieldChange={[MockFunction]}

+ 68
- 0
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/GitlabTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: with branch support 1`] = `
<div
className="bordered"
>
<AlmTab
alm="gitlab"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
}
}
definitions={
Array [
Object {
"key": "foo",
"personalAccessToken": "foobar",
},
]
}
features={
Array [
Object {
"active": true,
"description": "settings.almintegration.feature.mr_decoration.description",
"inactiveReason": "settings.almintegration.feature.need_at_least_1_binding",
"name": "settings.almintegration.feature.mr_decoration.title",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
<div
className="huge-spacer-top huge-spacer-bottom bordered-top"
/>
<div
className="big-padded"
>
<Connect(SubCategoryDefinitionsList)
category="almintegration"
subCategory="gitlab"
/>
</div>
</div>
`;

exports[`should render correctly: without branch support 1`] = `
<div
className="bordered"
>
<div
className="big-padded"
>
<Connect(SubCategoryDefinitionsList)
category="almintegration"
subCategory="gitlab"
/>
</div>
</div>
`;

+ 0
- 136
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmTabRenderer.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { AlmSettingsBinding, ALM_KEYS } from '../../../../types/alm-settings';
import AlmPRDecorationForm, { AlmPRDecorationFormChildrenProps } from './AlmPRDecorationForm';
import AlmPRDecorationTable from './AlmPRDecorationTable';

export interface AlmTabRendererProps<B> {
additionalColumnsHeaders: string[];
additionalColumnsKeys: Array<keyof B>;
alm: ALM_KEYS;
editedDefinition?: B;
defaultBinding: B;
definitions: B[];
form: (props: AlmPRDecorationFormChildrenProps<B>) => React.ReactNode;
loading: boolean;
multipleAlmEnabled: boolean;
onCancel: () => void;
onCreate: () => void;
onDelete: (definitionKey: string) => void;
onEdit: (definitionKey: string) => void;
onSubmit: (config: B, originalKey: string) => void;
success: boolean;
}

export default function AlmTabRenderer<B extends AlmSettingsBinding>(
props: AlmTabRendererProps<B>
) {
const {
additionalColumnsHeaders,
additionalColumnsKeys,
alm,
defaultBinding,
definitions,
editedDefinition,
form,
loading,
multipleAlmEnabled,
success
} = props;

let definition: B | undefined;
let mappedDefinitions: Array<{ key: string; additionalColumns: string[] }> = [];
let showEdit: boolean | undefined;

if (!multipleAlmEnabled) {
definition = editedDefinition;
if (definition === undefined && definitions.length > 0) {
definition = definitions[0];
}
showEdit = definition && editedDefinition === undefined;
} else {
mappedDefinitions = definitions.map(({ key, ...properties }) => {
const additionalColumns = additionalColumnsKeys.map(k => (properties as any)[k]);
return {
key,
additionalColumns
};
});
}

const help = (
<FormattedMessage
defaultMessage={translate(`settings.pr_decoration.${alm}.info`)}
id={`settings.pr_decoration.${alm}.info`}
values={{
link: (
<Link target="_blank" to="/documentation/analysis/pr-decoration/">
{translate('learn_more')}
</Link>
)
}}
/>
);

return multipleAlmEnabled ? (
<DeferredSpinner loading={loading}>
<AlmPRDecorationTable
additionalColumnsHeaders={additionalColumnsHeaders}
alm={alm}
definitions={mappedDefinitions}
onCreate={props.onCreate}
onDelete={props.onDelete}
onEdit={props.onEdit}
/>

{editedDefinition && (
<AlmPRDecorationForm
alm={alm}
bindingDefinition={editedDefinition}
help={help}
onCancel={props.onCancel}
onSubmit={props.onSubmit}
showInModal={true}>
{form}
</AlmPRDecorationForm>
)}
</DeferredSpinner>
) : (
<AlmPRDecorationForm
alm={alm}
bindingDefinition={definition || defaultBinding}
help={help}
hideKeyField={true}
loading={loading}
onCancel={props.onCancel}
onDelete={definition ? props.onDelete : undefined}
onEdit={showEdit ? props.onEdit : undefined}
onSubmit={props.onSubmit}
readOnly={showEdit}
success={success}>
{form}
</AlmPRDecorationForm>
);
}

+ 0
- 57
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/BitbucketTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import {
createBitbucketConfiguration,
updateBitbucketConfiguration
} from '../../../../api/almSettings';
import { ALM_KEYS, BitbucketBindingDefinition } from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import BitbucketForm from './BitbucketForm';

export interface BitbucketTabProps {
definitions: BitbucketBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function BitbucketTab(props: BitbucketTabProps) {
const { multipleAlmEnabled, definitions, loading } = props;

return (
<AlmTab
additionalColumnsHeaders={[translate('settings.pr_decoration.table.column.bitbucket.url')]}
additionalColumnsKeys={['url']}
alm={ALM_KEYS.BITBUCKET}
createConfiguration={createBitbucketConfiguration}
defaultBinding={{ key: '', url: '', personalAccessToken: '' }}
definitions={definitions}
form={childProps => <BitbucketForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateBitbucketConfiguration}
/>
);
}

+ 0
- 57
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GithubTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createGithubConfiguration, updateGithubConfiguration } from '../../../../api/almSettings';
import { ALM_KEYS, GithubBindingDefinition } from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import GithubForm from './GithubForm';

export interface GithubTabProps {
definitions: GithubBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function GithubTab(props: GithubTabProps) {
const { multipleAlmEnabled, definitions, loading } = props;

return (
<AlmTab
additionalColumnsHeaders={[
translate('settings.pr_decoration.table.column.github.url'),
translate('settings.pr_decoration.table.column.app_id')
]}
additionalColumnsKeys={['appId', 'url']}
alm={ALM_KEYS.GITHUB}
createConfiguration={createGithubConfiguration}
defaultBinding={{ key: '', appId: '', url: '', privateKey: '' }}
definitions={definitions}
form={childProps => <GithubForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGithubConfiguration}
/>
);
}

+ 0
- 51
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/GitlabTab.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { createGitlabConfiguration, updateGitlabConfiguration } from '../../../../api/almSettings';
import { ALM_KEYS, GitlabBindingDefinition } from '../../../../types/alm-settings';
import AlmTab from './AlmTab';
import GitlabForm from './GitlabForm';

export interface GitlabTabProps {
definitions: GitlabBindingDefinition[];
loading: boolean;
multipleAlmEnabled: boolean;
onDelete: (definitionKey: string) => void;
onUpdateDefinitions: () => void;
}

export default function GitlabTab(props: GitlabTabProps) {
const { multipleAlmEnabled, definitions, loading } = props;

return (
<AlmTab
alm={ALM_KEYS.GITLAB}
createConfiguration={createGitlabConfiguration}
defaultBinding={{ key: '', personalAccessToken: '' }}
definitions={definitions}
form={childProps => <GitlabForm {...childProps} />}
loading={loading}
multipleAlmEnabled={multipleAlmEnabled}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
updateConfiguration={updateGitlabConfiguration}
/>
);
}

+ 0
- 186
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/PRDecorationTabs.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import BoxedTabs from 'sonar-ui-common/components/controls/BoxedTabs';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
import { withAppState } from '../../../../components/hoc/withAppState';
import { AlmSettingsBindingDefinitions, ALM_KEYS } from '../../../../types/alm-settings';
import AzureTab from './AzureTab';
import BitbucketTab from './BitbucketTab';
import DeleteModal from './DeleteModal';
import GithubTab from './GithubTab';
import GitlabTab from './GitlabTab';

export interface PRDecorationTabsProps {
appState: Pick<T.AppState, 'multipleAlmEnabled'>;
currentAlm: ALM_KEYS;
definitionKeyForDeletion?: string;
definitions: AlmSettingsBindingDefinitions;
loading: boolean;
onCancel: () => void;
onConfirmDelete: (definitionKey: string) => void;
onDelete: (definitionKey: string) => void;
onSelectAlm: (alm: ALM_KEYS) => void;
onUpdateDefinitions: () => void;
projectCount?: number;
}

export const almName = {
[ALM_KEYS.AZURE]: 'Azure DevOps Server',
[ALM_KEYS.BITBUCKET]: 'Bitbucket Server',
[ALM_KEYS.GITHUB]: 'GitHub',
[ALM_KEYS.GITLAB]: 'GitLab'
};

export function PRDecorationTabs(props: PRDecorationTabsProps) {
const {
appState: { multipleAlmEnabled },
definitionKeyForDeletion,
definitions,
currentAlm,
loading,
projectCount
} = props;

return (
<>
<header className="page-header">
<h1 className="page-title">{translate('settings.pr_decoration.title')}</h1>
</header>

<div className="markdown small spacer-top big-spacer-bottom">
{translate('settings.pr_decoration.description')}
</div>
<BoxedTabs
onSelect={props.onSelectAlm}
selected={currentAlm}
tabs={[
{
key: ALM_KEYS.GITHUB,
label: (
<>
<img
alt="github"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/github.svg`}
/>
{almName[ALM_KEYS.GITHUB]}
</>
)
},
{
key: ALM_KEYS.BITBUCKET,
label: (
<>
<img
alt="bitbucket"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
/>
{almName[ALM_KEYS.BITBUCKET]}
</>
)
},
{
key: ALM_KEYS.AZURE,
label: (
<>
<img
alt="azure"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/azure.svg`}
/>
{almName[ALM_KEYS.AZURE]}
</>
)
},
{
key: ALM_KEYS.GITLAB,
label: (
<>
<img
alt="gitlab"
className="spacer-right"
height={16}
src={`${getBaseUrl()}/images/alm/gitlab.svg`}
/>
{almName[ALM_KEYS.GITLAB]}
</>
)
}
]}
/>

<div className="boxed-group boxed-group-inner">
{currentAlm === ALM_KEYS.AZURE && (
<AzureTab
definitions={definitions.azure}
loading={loading}
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === ALM_KEYS.BITBUCKET && (
<BitbucketTab
definitions={definitions.bitbucket}
loading={loading}
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === ALM_KEYS.GITHUB && (
<GithubTab
definitions={definitions.github}
loading={loading}
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
{currentAlm === ALM_KEYS.GITLAB && (
<GitlabTab
definitions={definitions.gitlab}
loading={loading}
multipleAlmEnabled={Boolean(multipleAlmEnabled)}
onDelete={props.onDelete}
onUpdateDefinitions={props.onUpdateDefinitions}
/>
)}
</div>

{definitionKeyForDeletion && (
<DeleteModal
id={definitionKeyForDeletion}
onCancel={props.onCancel}
onDelete={props.onConfirmDelete}
projectCount={projectCount}
/>
)}
</>
);
}

export default withAppState(PRDecorationTabs);

+ 0
- 121
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmPRDecorationFormRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.pr_decoration.form.save
</SubmitButton>
</div>
</form>
`;

exports[`should render correctly 2`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.pr_decoration.form.save
</SubmitButton>
<ResetButtonLink
className="spacer-left"
onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
</div>
</form>
`;

exports[`should render correctly 3`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.pr_decoration.form.save
</SubmitButton>
<Button
className="button-red spacer-left"
disabled={false}
onClick={[MockFunction]}
>
delete
</Button>
</div>
</form>
`;

exports[`should render correctly 4`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.pr_decoration.form.save
</SubmitButton>
<span
className="text-success spacer-left"
>
<AlertSuccessIcon
className="spacer-right"
/>
settings.state.saved
</span>
</div>
</form>
`;

exports[`should render correctly 5`] = `
<form
className="views-form"
data-test="settings__alm-form"
onSubmit={[Function]}
>
<Component />
<div
className="display-flex-center"
>
<SubmitButton
disabled={true}
>
settings.pr_decoration.form.save
</SubmitButton>
<DeferredSpinner
className="spacer-left"
timeout={100}
/>
</div>
</form>
`;

+ 0
- 254
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly for multi-ALM binding: editing a definition 1`] = `
<DeferredSpinner
loading={false}
timeout={100}
>
<AlmPRDecorationTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
<AlmPRDecorationForm
alm="github"
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.pr_decoration.github.info"
id="settings.pr_decoration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
onCancel={[MockFunction]}
onSubmit={[MockFunction]}
showInModal={true}
>
<Component />
</AlmPRDecorationForm>
</DeferredSpinner>
`;

exports[`should render correctly for multi-ALM binding: loaded 1`] = `
<DeferredSpinner
loading={false}
timeout={100}
>
<AlmPRDecorationTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
`;

exports[`should render correctly for multi-ALM binding: loading 1`] = `
<DeferredSpinner
loading={true}
timeout={100}
>
<AlmPRDecorationTable
additionalColumnsHeaders={
Array [
"url",
"app_id",
]
}
alm="github"
definitions={
Array [
Object {
"additionalColumns": Array [
"http://github.enterprise.com",
"123456",
],
"key": "key",
},
]
}
onCreate={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
/>
</DeferredSpinner>
`;

exports[`should render correctly for single-ALM binding 1`] = `
<AlmPRDecorationForm
alm="github"
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.pr_decoration.github.info"
id="settings.pr_decoration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={true}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmPRDecorationForm>
`;

exports[`should render correctly for single-ALM binding 2`] = `
<AlmPRDecorationForm
alm="github"
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.pr_decoration.github.info"
id="settings.pr_decoration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={false}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmPRDecorationForm>
`;

exports[`should render correctly for single-ALM binding 3`] = `
<AlmPRDecorationForm
alm="github"
bindingDefinition={
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
}
}
help={
<FormattedMessage
defaultMessage="settings.pr_decoration.github.info"
id="settings.pr_decoration.github.info"
values={
Object {
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/analysis/pr-decoration/"
>
learn_more
</Link>,
}
}
/>
}
hideKeyField={true}
loading={false}
onCancel={[MockFunction]}
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSubmit={[MockFunction]}
readOnly={true}
success={false}
>
<Component />
</AlmPRDecorationForm>
`;

+ 0
- 28
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/AzureTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<AlmTab
alm="azure"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
}
}
definitions={
Array [
Object {
"key": "key",
"personalAccessToken": "asdf1234",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
`;

+ 0
- 40
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/BitbucketTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<AlmTab
additionalColumnsHeaders={
Array [
"settings.pr_decoration.table.column.bitbucket.url",
]
}
additionalColumnsKeys={
Array [
"url",
]
}
alm="bitbucket"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
"url": "",
}
}
definitions={
Array [
Object {
"key": "key",
"personalAccessToken": "asdf1234",
"url": "http://bbs.enterprise.com",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
`;

+ 0
- 44
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GithubTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<AlmTab
additionalColumnsHeaders={
Array [
"settings.pr_decoration.table.column.github.url",
"settings.pr_decoration.table.column.app_id",
]
}
additionalColumnsKeys={
Array [
"appId",
"url",
]
}
alm="github"
createConfiguration={[Function]}
defaultBinding={
Object {
"appId": "",
"key": "",
"privateKey": "",
"url": "",
}
}
definitions={
Array [
Object {
"appId": "123456",
"key": "key",
"privateKey": "asdf1234",
"url": "http://github.enterprise.com",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
`;

+ 0
- 28
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/__tests__/__snapshots__/GitlabTab-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<AlmTab
alm="gitlab"
createConfiguration={[Function]}
defaultBinding={
Object {
"key": "",
"personalAccessToken": "",
}
}
definitions={
Array [
Object {
"key": "foo",
"personalAccessToken": "foobar",
},
]
}
form={[Function]}
loading={false}
multipleAlmEnabled={true}
onDelete={[MockFunction]}
onUpdateDefinitions={[MockFunction]}
updateConfiguration={[Function]}
/>
`;

+ 12
- 12
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx View File

setProjectBitbucketBinding, setProjectBitbucketBinding,
setProjectGithubBinding, setProjectGithubBinding,
setProjectGitlabBinding setProjectGitlabBinding
} from '../../../../api/almSettings';
} from '../../../../api/alm-settings';
import throwGlobalError from '../../../../app/utils/throwGlobalError'; import throwGlobalError from '../../../../app/utils/throwGlobalError';
import { AlmSettingsInstance, ALM_KEYS, ProjectAlmBinding } from '../../../../types/alm-settings';
import { AlmKeys, AlmSettingsInstance, ProjectAlmBinding } from '../../../../types/alm-settings';
import PRDecorationBindingRenderer from './PRDecorationBindingRenderer'; import PRDecorationBindingRenderer from './PRDecorationBindingRenderer';


interface Props { interface Props {
success: boolean; success: boolean;
} }


const FIELDS_BY_ALM: { [almKey in ALM_KEYS]: Array<keyof T.Omit<ProjectAlmBinding, 'key'>> } = {
[ALM_KEYS.AZURE]: [],
[ALM_KEYS.BITBUCKET]: ['repository', 'slug'],
[ALM_KEYS.GITHUB]: ['repository'],
[ALM_KEYS.GITLAB]: []
const FIELDS_BY_ALM: { [almKey in AlmKeys]: Array<keyof T.Omit<ProjectAlmBinding, 'key'>> } = {
[AlmKeys.Azure]: [],
[AlmKeys.Bitbucket]: ['repository', 'slug'],
[AlmKeys.GitHub]: ['repository'],
[AlmKeys.GitLab]: []
}; };


export default class PRDecorationBinding extends React.PureComponent<Props, State> { export default class PRDecorationBinding extends React.PureComponent<Props, State> {
}; };


submitProjectAlmBinding( submitProjectAlmBinding(
alm: ALM_KEYS,
alm: AlmKeys,
key: string, key: string,
almSpecificFields?: T.Omit<ProjectAlmBinding, 'key'> almSpecificFields?: T.Omit<ProjectAlmBinding, 'key'>
): Promise<void> { ): Promise<void> {
const project = this.props.component.key; const project = this.props.component.key;


switch (alm) { switch (alm) {
case ALM_KEYS.AZURE:
case AlmKeys.Azure:
return setProjectAzureBinding({ return setProjectAzureBinding({
almSetting, almSetting,
project project
}); });
case ALM_KEYS.BITBUCKET: {
case AlmKeys.Bitbucket: {
if (!almSpecificFields) { if (!almSpecificFields) {
return Promise.reject(); return Promise.reject();
} }
slug slug
}); });
} }
case ALM_KEYS.GITHUB: {
case AlmKeys.GitHub: {
const repository = almSpecificFields && almSpecificFields.repository; const repository = almSpecificFields && almSpecificFields.repository;
if (!repository) { if (!repository) {
return Promise.reject(); return Promise.reject();
}); });
} }


case ALM_KEYS.GITLAB:
case AlmKeys.GitLab:
return setProjectGitlabBinding({ return setProjectGitlabBinding({
almSetting, almSetting,
project project

+ 3
- 3
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx View File

import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { Alert } from 'sonar-ui-common/components/ui/Alert';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { AlmSettingsInstance, ALM_KEYS, ProjectAlmBinding } from '../../../../types/alm-settings';
import { AlmKeys, AlmSettingsInstance, ProjectAlmBinding } from '../../../../types/alm-settings';


export interface PRDecorationBindingRendererProps { export interface PRDecorationBindingRendererProps {
formData: ProjectAlmBinding; formData: ProjectAlmBinding;
/> />
</div> </div>


{alm === ALM_KEYS.BITBUCKET && (
{alm === AlmKeys.Bitbucket && (
<> <>
{renderField({ {renderField({
help: true, help: true,
</> </>
)} )}


{alm === ALM_KEYS.GITHUB &&
{alm === AlmKeys.GitHub &&
renderField({ renderField({
help: true, help: true,
helpParams: { example: 'SonarSource/sonarqube' }, helpParams: { example: 'SonarSource/sonarqube' },

+ 10
- 10
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx View File

setProjectAzureBinding, setProjectAzureBinding,
setProjectBitbucketBinding, setProjectBitbucketBinding,
setProjectGithubBinding setProjectGithubBinding
} from '../../../../../api/almSettings';
} from '../../../../../api/alm-settings';
import { mockComponent } from '../../../../../helpers/testMocks'; import { mockComponent } from '../../../../../helpers/testMocks';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import PRDecorationBinding from '../PRDecorationBinding'; import PRDecorationBinding from '../PRDecorationBinding';


jest.mock('../../../../../api/almSettings', () => ({
jest.mock('../../../../../api/alm-settings', () => ({
getAlmSettings: jest.fn().mockResolvedValue([]), getAlmSettings: jest.fn().mockResolvedValue([]),
getProjectAlmBinding: jest.fn().mockResolvedValue(undefined), getProjectAlmBinding: jest.fn().mockResolvedValue(undefined),
setProjectAzureBinding: jest.fn().mockResolvedValue(undefined), setProjectAzureBinding: jest.fn().mockResolvedValue(undefined),


it('should fill selects and fill formdata', async () => { it('should fill selects and fill formdata', async () => {
const url = 'github.com'; const url = 'github.com';
const instances = [{ key: 'instance1', url, alm: ALM_KEYS.GITHUB }];
const instances = [{ key: 'instance1', url, alm: AlmKeys.GitHub }];
const formdata = { const formdata = {
key: 'instance1', key: 'instance1',
repository: 'account/repo' repository: 'account/repo'


describe('handleSubmit', () => { describe('handleSubmit', () => {
const instances = [ const instances = [
{ key: 'github', alm: ALM_KEYS.GITHUB },
{ key: 'azure', alm: ALM_KEYS.AZURE },
{ key: 'bitbucket', alm: ALM_KEYS.BITBUCKET }
{ key: 'github', alm: AlmKeys.GitHub },
{ key: 'azure', alm: AlmKeys.Azure },
{ key: 'bitbucket', alm: AlmKeys.Bitbucket }
]; ];


it('should work for github', async () => { it('should work for github', async () => {


wrapper.setState({ wrapper.setState({
instances: [ instances: [
{ key: 'azure', alm: ALM_KEYS.AZURE },
{ key: 'bitbucket', alm: ALM_KEYS.BITBUCKET },
{ key: 'github', alm: ALM_KEYS.GITHUB }
{ key: 'azure', alm: AlmKeys.Azure },
{ key: 'bitbucket', alm: AlmKeys.Bitbucket },
{ key: 'github', alm: AlmKeys.GitHub }
] ]
}); });
expect(wrapper.instance().validateForm({ key: 'azure' })).toBe(true); expect(wrapper.instance().validateForm({ key: 'azure' })).toBe(true);

+ 9
- 9
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx View File

import * as React from 'react'; import * as React from 'react';
import Select from 'sonar-ui-common/components/controls/Select'; import Select from 'sonar-ui-common/components/controls/Select';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { ALM_KEYS } from '../../../../../types/alm-settings';
import { AlmKeys } from '../../../../../types/alm-settings';
import PRDecorationBindingRenderer, { import PRDecorationBindingRenderer, {
PRDecorationBindingRendererProps PRDecorationBindingRendererProps
} from '../PRDecorationBindingRenderer'; } from '../PRDecorationBindingRenderer';
const singleInstance = { const singleInstance = {
key: 'single', key: 'single',
url: 'http://single.url', url: 'http://single.url',
alm: ALM_KEYS.GITHUB
alm: AlmKeys.GitHub
}; };
expect( expect(
shallowRender({ shallowRender({
const urls = ['http://github.enterprise.com', 'http://bbs.enterprise.com']; const urls = ['http://github.enterprise.com', 'http://bbs.enterprise.com'];
const instances = [ const instances = [
{ {
alm: ALM_KEYS.GITHUB,
alm: AlmKeys.GitHub,
key: 'i1', key: 'i1',
url: urls[0] url: urls[0]
}, },
{ {
alm: ALM_KEYS.GITHUB,
alm: AlmKeys.GitHub,
key: 'i2', key: 'i2',
url: urls[0] url: urls[0]
}, },
{ {
alm: ALM_KEYS.BITBUCKET,
alm: AlmKeys.Bitbucket,
key: 'i3', key: 'i3',
url: urls[1] url: urls[1]
}, },
{ {
alm: ALM_KEYS.AZURE,
alm: AlmKeys.Azure,
key: 'i4' key: 'i4'
} }
]; ];


it('should display action state correctly', () => { it('should display action state correctly', () => {
const urls = ['http://url.com']; const urls = ['http://url.com'];
const instances = [{ key: 'key', url: urls[0], alm: ALM_KEYS.GITHUB }];
const instances = [{ key: 'key', url: urls[0], alm: AlmKeys.GitHub }];


expect(shallowRender({ instances, loading: false, saving: true })).toMatchSnapshot(); expect(shallowRender({ instances, loading: false, saving: true })).toMatchSnapshot();
expect(shallowRender({ instances, loading: false, success: true })).toMatchSnapshot(); expect(shallowRender({ instances, loading: false, success: true })).toMatchSnapshot();
it('should render select options correctly', async () => { it('should render select options correctly', async () => {
const instances = [ const instances = [
{ {
alm: ALM_KEYS.AZURE,
alm: AlmKeys.Azure,
key: 'azure' key: 'azure'
}, },
{ {
alm: ALM_KEYS.GITHUB,
alm: AlmKeys.GitHub,
key: 'github', key: 'github',
url: 'gh.url.com' url: 'gh.url.com'
} }

+ 2
- 2
server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { import {
AlmKeys,
AlmSettingsInstance, AlmSettingsInstance,
ALM_KEYS,
AzureBindingDefinition, AzureBindingDefinition,
BitbucketBindingDefinition, BitbucketBindingDefinition,
GithubBindingDefinition, GithubBindingDefinition,
overrides: Partial<AlmSettingsInstance> = {} overrides: Partial<AlmSettingsInstance> = {}
): AlmSettingsInstance { ): AlmSettingsInstance {
return { return {
alm: ALM_KEYS.GITHUB,
alm: AlmKeys.GitHub,
key: 'key', key: 'key',
...overrides ...overrides
}; };

+ 23
- 23
server/sonar-web/src/main/js/types/alm-settings.ts View File

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
export const enum ALM_KEYS {
AZURE = 'azure',
BITBUCKET = 'bitbucket',
GITHUB = 'github',
GITLAB = 'gitlab'
export const enum AlmKeys {
Azure = 'azure',
Bitbucket = 'bitbucket',
GitHub = 'github',
GitLab = 'gitlab'
} }


export interface AlmSettingsBinding {
export interface AlmBindingDefinition {
key: string; key: string;
} }


export interface AlmSettingsInstance {
alm: ALM_KEYS;
key: string;
url?: string;
}

export interface AlmSettingsBindingDefinitions {
azure: AzureBindingDefinition[];
bitbucket: BitbucketBindingDefinition[];
github: GithubBindingDefinition[];
gitlab: GitlabBindingDefinition[];
}

export interface AzureBindingDefinition extends AlmSettingsBinding {
export interface AzureBindingDefinition extends AlmBindingDefinition {
personalAccessToken: string; personalAccessToken: string;
} }


export interface BitbucketBindingDefinition extends AlmSettingsBinding {
export interface BitbucketBindingDefinition extends AlmBindingDefinition {
personalAccessToken: string; personalAccessToken: string;
url: string; url: string;
} }


export interface GithubBindingDefinition extends AlmSettingsBinding {
export interface GithubBindingDefinition extends AlmBindingDefinition {
appId: string; appId: string;
privateKey: string; privateKey: string;
url: string; url: string;
} }


export interface GitlabBindingDefinition extends AlmSettingsBinding {
export interface GitlabBindingDefinition extends AlmBindingDefinition {
personalAccessToken: string; personalAccessToken: string;
} }


almSetting: string; almSetting: string;
project: string; project: string;
} }

export interface AlmSettingsInstance {
alm: AlmKeys;
key: string;
url?: string;
}

export interface AlmSettingsBindingDefinitions {
azure: AzureBindingDefinition[];
bitbucket: BitbucketBindingDefinition[];
github: GithubBindingDefinition[];
gitlab: GitlabBindingDefinition[];
}

+ 63
- 53
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File



settings.languages.select_a_language_placeholder=Select a language settings.languages.select_a_language_placeholder=Select a language


settings.pr_decoration.category=Pull Request Decoration
settings.pr_decoration.title=Pull Request Decoration
settings.pr_decoration.description=Pull Request Decoration adds SonarQube analysis and a Quality Gate to your Pull Requests directly in your ALM provider's interface.
settings.pr_decoration.manage_instances=Manage instances
settings.pr_decoration.azure.info=Accounts that will be used to decorate Pull Requests need Code: Read & Write permission. {link}
settings.pr_decoration.bitbucket.info=Accounts that will be used to decorate Pull Requests need 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.gitlab.info=Accounts that will be used to decorate Merge Requests need comment permissions on projects. The personal key needs the API scope permission. {link}
settings.pr_decoration.table.title=Pull Request Decoration configurations
settings.mr_decoration.table.title=Merge Request Decoration configurations
settings.pr_decoration.table.empty.azure=Create your first Azure DevOps configuration to enable Pull Request Decoration on your projects.
settings.pr_decoration.table.empty.bitbucket=Create your first Bitbucket configuration to enable Pull Request Decoration on your projects.
settings.pr_decoration.table.empty.github=Create your first GitHub configuration to enable Pull Request Decoration on your organization or repository.
settings.pr_decoration.table.empty.gitlab=Create your first GitLab configuration to enable Merge Request Decoration on your repository.
settings.pr_decoration.table.create=Create configuration
settings.pr_decoration.table.column.name=Name
settings.pr_decoration.table.column.bitbucket.url=Bitbucket Server URL
settings.pr_decoration.table.column.github.url=GitHub Enterprise or GitHub.com URL
settings.pr_decoration.table.column.app_id=App ID
settings.pr_decoration.table.column.edit=Edit
settings.pr_decoration.table.column.delete=Delete
settings.pr_decoration.delete.header=Delete configuration
settings.pr_decoration.delete.message=Are you sure you want to delete the {id} configuration?
settings.pr_decoration.delete.info={0} projects will no longer get Pull Request Decorations.
settings.pr_decoration.delete.no_info=An unknown number of projects will no longer get Pull Request Decorations.
settings.pr_decoration.form.header.create=Create a Pull Request Decoration configuration
settings.mr_decoration.form.header.create=Create a Merge Request Decoration configuration
settings.pr_decoration.form.header.edit=Edit the Pull Request Decoration configuration
settings.mr_decoration.form.header.edit=Edit the Merge Request Decoration configuration
settings.pr_decoration.form.name.azure=Configuration name
settings.pr_decoration.form.name.azure.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Azure instance for a project.
settings.pr_decoration.form.name.bitbucket=Configuration name
settings.pr_decoration.form.name.bitbucket.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Bitbucket instance for a project.
settings.pr_decoration.form.name.github=Configuration name
settings.pr_decoration.form.name.github.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitHub App for a project.
settings.pr_decoration.form.name.gitlab=Configuration name
settings.pr_decoration.form.name.gitlab.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitLab instance for a project.
settings.pr_decoration.form.url.bitbucket=Bitbucket Server URL
settings.pr_decoration.form.url.bitbucket.help=Example: {example}
settings.pr_decoration.form.url.github=GitHub URL
settings.pr_decoration.form.url.github.help1=Example for Github Enterprise:
settings.pr_decoration.form.url.github.help2=If using GitHub.com:
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.azure.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.personal_access_token.gitlab.help=Token of the user that will be used to decorate the Merge Requests. Needs API scope authorization.
settings.pr_decoration.form.save=Save configuration
settings.pr_decoration.form.cancel=Cancel
settings.almintegration.title=Integration configurations
settings.almintegration.description=ALM integrations allow SonarQube to interact with your ALM. This enables things like authentication, or providing analysis details and a Quality Gate to your Pull Requests directly in your ALM provider's interface.
settings.almintegration.manage_instances=Manage instances
settings.almintegration.azure.info=Accounts that will be used to decorate Pull Requests need Code: Read & Write permission. {link}
settings.almintegration.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.almintegration.gitlab.info=Accounts that will be used to decorate Merge Requests need comment permissions on projects. The personal key needs the API scope permission. {link}
settings.almintegration.bitbucket.help_1=SonarQube needs a Personal Access Token to communicate with Bitbucket Server. This token will be used to decorate Pull Requests.
settings.almintegration.bitbucket.help_2=The account used for integration needs write permission.
settings.almintegration.bitbucket.help_3=We recommend to integrate with SonarQube using a Bitbucket Server Service Account.
settings.almintegration.table.title=ALM integration configurations
settings.almintegration.table.empty.azure=Create your first Azure DevOps configuration to enable Pull Request Decoration on your projects.
settings.almintegration.table.empty.bitbucket=Create your first Bitbucket configuration to enable Pull Request Decoration on your projects.
settings.almintegration.table.empty.github=Create your first GitHub configuration to enable Pull Request Decoration on your organization or repository.
settings.almintegration.table.empty.gitlab=Create your first GitLab configuration to enable Merge Request Decoration on your repository.
settings.almintegration.table.create=Create configuration
settings.almintegration.table.column.name=Name
settings.almintegration.table.column.bitbucket.url=Bitbucket Server URL
settings.almintegration.table.column.github.url=GitHub Enterprise or GitHub.com URL
settings.almintegration.table.column.app_id=App ID
settings.almintegration.table.column.edit=Edit
settings.almintegration.table.column.delete=Delete
settings.almintegration.delete.header=Delete configuration
settings.almintegration.delete.message=Are you sure you want to delete the {id} configuration?
settings.almintegration.delete.info={0} projects will no longer get Pull Request Decorations.
settings.almintegration.delete.no_info=An unknown number of projects will no longer get Pull Request Decorations.
settings.almintegration.form.header.create=Create a configuration
settings.almintegration.form.header.edit=Edit the configuration
settings.almintegration.form.name.azure=Configuration name
settings.almintegration.form.name.azure.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Azure instance for a project.
settings.almintegration.form.name.bitbucket=Configuration name
settings.almintegration.form.name.bitbucket.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured Bitbucket instance for a project.
settings.almintegration.form.name.github=Configuration name
settings.almintegration.form.name.github.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitHub App for a project.
settings.almintegration.form.name.gitlab=Configuration name
settings.almintegration.form.name.gitlab.help=Give your configuration a clear and succinct name. This name will be used at project level to identify the correct configured GitLab instance for a project.
settings.almintegration.form.url.bitbucket=Bitbucket Server URL
settings.almintegration.form.url.bitbucket.help=Example: {example}
settings.almintegration.form.url.github=GitHub URL
settings.almintegration.form.url.github.help1=Example for Github Enterprise:
settings.almintegration.form.url.github.help2=If using GitHub.com:
settings.almintegration.form.app_id=GitHub App ID
settings.almintegration.form.private_key=Private Key
settings.almintegration.form.personal_access_token=Personal Access token
settings.almintegration.form.personal_access_token.azure.help=Token of the user that will be used to decorate the Pull Requests. Needs authorized scope: "Code (read and write)".
settings.almintegration.form.personal_access_token.gitlab.help=Token of the user that will be used to decorate the Merge Requests. Needs API scope authorization.
settings.almintegration.form.save=Save configuration
settings.almintegration.form.cancel=Cancel
settings.almintegration.features=ALM integration features
settings.almintegration.feature.enabled=This feature is enabled
settings.almintegration.feature.disabled=This feature is currently disabled
settings.almintegration.feature.need_at_least_1_binding=You need to have at least 1 binding configured to use this feature
settings.almintegration.feature.pr_decoration.title=Pull Request Decoration
settings.almintegration.feature.pr_decoration.description=Add analysis and a Quality Gate to your Pull Requests directly in your ALM provider's interface.
settings.almintegration.feature.mr_decoration.title=Merge Request Decoration
settings.almintegration.feature.mr_decoration.description=Add analysis and a Quality Gate to your Merge Requests directly in your ALM provider's interface.
settings.almintegration.feature.alm_repo_import.title=Import repositories from your ALM
settings.almintegration.feature.alm_repo_import.description=Select repositories from your ALM, and import them into SonarQube.
settings.almintegration.feature.alm_repo_import.disabled_if_multiple_bbs_instances=Connecting to multiple Bitbucket Server instances will deactivate the {feature} feature. Projects will have to be set up manually.


settings.pr_decoration.binding.category=Pull Request Decoration settings.pr_decoration.binding.category=Pull Request Decoration
settings.pr_decoration.binding.no_bindings=This feature must first be enabled in the global settings. {link} settings.pr_decoration.binding.no_bindings=This feature must first be enabled in the global settings. {link}
property.category.general.looknfeel=Look & Feel property.category.general.looknfeel=Look & Feel
property.category.general.issues=Issues property.category.general.issues=Issues
property.category.general.subProjects=Sub-projects property.category.general.subProjects=Sub-projects
property.category.almintegration=ALM Integrations
property.category.almintegration.github=GitHub Authentication
property.category.almintegration.github.description=In order to enable authentication on GitHub.com or GitHub Enterprise:<ul><li>SonarQube must be publicly accessible through HTTPS only</li><li>The property 'sonar.core.serverBaseURL' must be set to this public HTTPS URL</li><li>In your GitHub profile, you need to create a Developer Application for which the 'Authorization callback URL' must be set to <code>'&lt;value_of_sonar.core.serverBaseURL_property&gt;/oauth2/callback'</code>.</li></ul>
property.category.almintegration.gitlab=GitLab Authentication
property.category.almintegration.gitlab.description=In order to enable GitLab authentication, the property 'sonar.core.serverBaseURL' must be set to the public URL
property.category.organizations=Organizations property.category.organizations=Organizations
property.category.security=Security property.category.security=Security
property.category.security.encryption=Encryption property.category.security.encryption=Encryption
property.category.security.github=GitHub
property.category.security.github.description=In order to enable authentication on GitHub.com or GitHub Enterprise:<ul><li>SonarQube must be publicly accessible through HTTPS only</li><li>The property 'sonar.core.serverBaseURL' must be set to this public HTTPS URL</li><li>In your GitHub profile, you need to create a Developer Application for which the 'Authorization callback URL' must be set to <code>'&lt;value_of_sonar.core.serverBaseURL_property&gt;/oauth2/callback'</code>.</li></ul>
property.category.security.gitlab=Gitlab
property.category.security.gitlab.description=In order to enable Gitlab authentication, the property 'sonar.core.serverBaseURL' must be set to the public URL
property.category.security.saml=SAML property.category.security.saml=SAML
property.category.security.saml.description=In order to enable SAML authentication, the property 'sonar.core.serverBaseURL' must be set to the public URL property.category.security.saml.description=In order to enable SAML authentication, the property 'sonar.core.serverBaseURL' must be set to the public URL
property.category.java=Java property.category.java=Java

+ 5
- 0
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java View File

*/ */
String SUBCATEGORY_DUPLICATIONS = "duplications"; String SUBCATEGORY_DUPLICATIONS = "duplications";


/**
* @since 8.2
*/
String CATEGORY_ALM_INTEGRATION = "almintegration";

/** /**
* @since 8.1 * @since 8.1
*/ */

Loading…
Cancel
Save