From a578ffdedd7c3ec10cf636bc4a8d3a70188174e6 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 18 Oct 2019 13:50:58 +0200 Subject: [PATCH] SONAR-12512 UI project-level form --- .../main/js/api/__tests__/almSettings-test.ts | 71 --- .../sonar-web/src/main/js/api/almSettings.ts | 30 +- .../components/AdditionalCategories.tsx | 17 +- .../components/AdditionalCategoryKeys.ts | 1 + .../AlmPRDecorationFormModal.tsx | 16 +- .../AlmPRDecorationFormModalRenderer.tsx | 8 +- .../pullRequestDecoration/GithubTab.tsx | 11 +- .../PRDecorationTable.tsx | 8 +- .../PRDecorationTabs.tsx | 5 +- .../PullRequestDecoration.tsx | 4 +- .../pullRequestDecoration/TabRenderer.tsx | 12 +- .../AlmPRDecorationFormModal-test.tsx | 4 +- ...RDecorationFormModalRenderer-test.tsx.snap | 4 + .../PRDecorationTabs-test.tsx.snap | 6 - .../__snapshots__/TabRenderer-test.tsx.snap | 2 +- .../PRDecorationBinding.tsx | 182 +++++++ .../PRDecorationBindingRenderer.tsx | 147 ++++++ .../__tests__/PRDecorationBinding-test.tsx | 174 +++++++ .../PRDecorationBindingRenderer-test.tsx | 121 +++++ .../PRDecorationBinding-test.tsx.snap | 21 + .../PRDecorationBindingRenderer-test.tsx.snap | 493 ++++++++++++++++++ .../src/main/js/helpers/testMocks.ts | 4 +- .../src/main/js/types/alm-settings.d.ts | 56 ++ .../src/main/js/{app => types}/types.d.ts | 14 - .../resources/org/sonar/l10n/core.properties | 12 +- 25 files changed, 1286 insertions(+), 137 deletions(-) delete mode 100644 server/sonar-web/src/main/js/api/__tests__/almSettings-test.ts create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/types/alm-settings.d.ts rename server/sonar-web/src/main/js/{app => types}/types.d.ts (98%) diff --git a/server/sonar-web/src/main/js/api/__tests__/almSettings-test.ts b/server/sonar-web/src/main/js/api/__tests__/almSettings-test.ts deleted file mode 100644 index d6eb4c8e014..00000000000 --- a/server/sonar-web/src/main/js/api/__tests__/almSettings-test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { getJSON } from 'sonar-ui-common/helpers/request'; -import { getAlmOrganization } from '../alm-integration'; - -jest.useFakeTimers(); -jest.mock('sonar-ui-common/helpers/request', () => ({ - ...jest.requireActual('sonar-ui-common/helpers/request'), - getJSON: jest.fn() -})); -jest.mock('../../app/utils/throwGlobalError', () => ({ - default: jest.fn().mockImplementation(r => Promise.reject(r)) -})); - -beforeEach(() => { - jest.clearAllTimers(); - jest.clearAllMocks(); -}); - -describe('getAlmOrganization', () => { - it('should return the organization', () => { - const response = { almOrganization: { key: 'foo', name: 'Foo' } }; - (getJSON as jest.Mock).mockResolvedValue(response); - return expect(getAlmOrganization({ installationId: 'foo' })).resolves.toEqual(response); - }); - - it('should reject with an error', () => { - const error = { status: 401 }; - (getJSON as jest.Mock).mockRejectedValue(error); - return expect(getAlmOrganization({ installationId: 'foo' })).rejects.toEqual(error); - }); - - it('should try until getting the organization', async () => { - (getJSON as jest.Mock).mockRejectedValue({ status: 404 }); - const spy = jest.fn(); - getAlmOrganization({ installationId: 'foo' }).then(spy); - for (let i = 1; i < 5; i++) { - expect(getJSON).toBeCalledTimes(i); - expect(spy).not.toBeCalled(); - await new Promise(setImmediate); - jest.runAllTimers(); - } - expect(getJSON).toBeCalledTimes(5); - expect(spy).not.toBeCalled(); - - const response = { almOrganization: { key: 'foo', name: 'Foo' } }; - (getJSON as jest.Mock).mockResolvedValue(response); - await new Promise(setImmediate); - jest.runAllTimers(); - expect(getJSON).toBeCalledTimes(6); - await new Promise(setImmediate); - expect(spy).toBeCalledWith(response); - }); -}); diff --git a/server/sonar-web/src/main/js/api/almSettings.ts b/server/sonar-web/src/main/js/api/almSettings.ts index 9e0e18b35be..655394cd658 100644 --- a/server/sonar-web/src/main/js/api/almSettings.ts +++ b/server/sonar-web/src/main/js/api/almSettings.ts @@ -20,15 +20,21 @@ import { getJSON, post } from 'sonar-ui-common/helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; -export function getAlmDefinitions(): Promise { +export function getAlmDefinitions(): Promise { return getJSON('/api/alm_settings/list_definitions').catch(throwGlobalError); } -export function createGithubConfiguration(data: T.GithubDefinition) { +export function getAlmSettings(project: string): Promise { + return getJSON('/api/alm_settings/list', { project }) + .then(({ almSettings }) => almSettings) + .catch(throwGlobalError); +} + +export function createGithubConfiguration(data: T.GithubBindingDefinition) { return post('/api/alm_settings/create_github', data).catch(throwGlobalError); } -export function updateGithubConfiguration(data: T.GithubDefinition & { newKey: string }) { +export function updateGithubConfiguration(data: T.GithubBindingDefinition & { newKey: string }) { return post('/api/alm_settings/update_github', data).catch(throwGlobalError); } @@ -36,6 +42,20 @@ export function deleteConfiguration(key: string) { return post('/api/alm_settings/delete', { key }).catch(throwGlobalError); } -export function countBindedProjects(instance: string) { - return getJSON('/api/alm_settings/count_binding', { instance }).catch(throwGlobalError); +export function countBindedProjects(almSetting: string) { + return getJSON('/api/alm_settings/count_binding', { almSetting }) + .then(({ projects }) => projects) + .catch(throwGlobalError); +} + +export function getProjectAlmBinding(project: string): Promise { + return getJSON('/api/alm_settings/get_github_binding', { project }); +} + +export function deleteProjectAlmBinding(project: string): Promise { + return post('/api/alm_settings/delete_binding', { project }).catch(throwGlobalError); +} + +export function setProjectAlmBinding(data: T.GithubProjectAlmBinding) { + return post('/api/alm_settings/set_github_binding', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx index 8902f88ace1..92025b7dec3 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx @@ -24,12 +24,14 @@ import { ANALYSIS_SCOPE_CATEGORY, LANGUAGES_CATEGORY, NEW_CODE_PERIOD_CATEGORY, + PULL_REQUEST_DECORATION_BINDING_CATEGORY, PULL_REQUEST_DECORATION_CATEGORY } from './AdditionalCategoryKeys'; import { AnalysisScope } from './AnalysisScope'; import Languages from './Languages'; import NewCodePeriod from './NewCodePeriod'; import PullRequestDecoration from './pullRequestDecoration/PullRequestDecoration'; +import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding'; export interface AdditionalCategoryComponentProps { parentComponent: T.Component | undefined; @@ -39,7 +41,7 @@ export interface AdditionalCategoryComponentProps { export interface AdditionalCategory { key: string; name: string; - renderComponent: (props: AdditionalCategoryComponentProps) => JSX.Element; + renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode; availableGlobally: boolean; availableForProject: boolean; displayTab: boolean; @@ -77,6 +79,14 @@ export const ADDITIONAL_CATEGORIES: AdditionalCategory[] = [ availableGlobally: true, availableForProject: false, displayTab: true + }, + { + key: PULL_REQUEST_DECORATION_BINDING_CATEGORY, + name: translate('settings.pr_decoration.binding.category'), + renderComponent: getPullRequestDecorationBindingComponent, + availableGlobally: false, + availableForProject: true, + displayTab: true } ]; @@ -95,3 +105,8 @@ function getAnalysisScopeComponent(props: AdditionalCategoryComponentProps) { function getPullRequestDecorationComponent() { return ; } + +function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) { + const { parentComponent } = props; + return parentComponent && ; +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts index 742ebb58c89..4d5b7c32a18 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts +++ b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategoryKeys.ts @@ -22,3 +22,4 @@ export const ANALYSIS_SCOPE_CATEGORY = 'exclusions'; export const LANGUAGES_CATEGORY = 'languages'; 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'; diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModal.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModal.tsx index 781f3f068d7..6eb11a4c0cb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModal.tsx @@ -22,23 +22,23 @@ import AlmPRDecorationFormModalRenderer from './AlmPRDecorationFormModalRenderer interface Props { alm: string; - data: T.GithubDefinition; + bindingDefinition: T.GithubBindingDefinition; onCancel: () => void; - onSubmit: (data: T.GithubDefinition, originalKey: string) => void; + onSubmit: (bindingDefinition: T.GithubBindingDefinition, originalKey: string) => void; } interface State { - formData: T.GithubDefinition; + formData: T.GithubBindingDefinition; } export default class AlmPRDecorationFormModal extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { formData: props.data }; + this.state = { formData: props.bindingDefinition }; } - handleFieldChange = (fieldId: keyof T.GithubDefinition, value: string) => { + handleFieldChange = (fieldId: keyof T.GithubBindingDefinition, value: string) => { this.setState(({ formData }) => ({ formData: { ...formData, @@ -48,7 +48,7 @@ export default class AlmPRDecorationFormModal extends React.PureComponent { - this.props.onSubmit(this.state.formData, this.props.data.key); + this.props.onSubmit(this.state.formData, this.props.bindingDefinition.key); }; canSubmit = () => { @@ -59,7 +59,7 @@ export default class AlmPRDecorationFormModal extends React.PureComponent ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx index d110c3c327a..08993b527e0 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecoration/AlmPRDecorationFormModalRenderer.tsx @@ -28,7 +28,7 @@ import { ALM_KEYS } from '../../utils'; export interface AlmPRDecorationFormModalProps { alm: string; canSubmit: () => boolean; - formData: T.GithubDefinition; + formData: T.GithubBindingDefinition; onCancel: () => void; onSubmit: () => void; onFieldChange: (id: string, value: string) => void; @@ -37,18 +37,18 @@ export interface AlmPRDecorationFormModalProps { function renderField(params: { autoFocus?: boolean; - formData: T.GithubDefinition; + formData: T.GithubBindingDefinition; help: boolean; id: string; isTextArea: boolean; maxLength: number; onFieldChange: (id: string, value: string) => void; - propKey: keyof T.GithubDefinition; + propKey: keyof T.GithubBindingDefinition; }) { const { autoFocus, formData, help, id, isTextArea, maxLength, onFieldChange, propKey } = params; return (
-