From: Wouter Admiraal Date: Mon, 31 May 2021 14:22:08 +0000 (+0200) Subject: SONAR-14872 Display warning if PR deco cannot happen X-Git-Tag: 9.0.0.45539~120 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=2902cdedeafb9803bc21464a4b50a05f2a7e0ded;p=sonarqube.git SONAR-14872 Display warning if PR deco cannot happen --- diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index caf68e4eb0e..d1cf0ae7c18 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -20,11 +20,13 @@ import { differenceBy } from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; -import { getProjectAlmBinding } from '../../api/alm-settings'; +import { HttpStatus } from 'sonar-ui-common/helpers/request'; +import { getProjectAlmBinding, validateProjectAlmBinding } from '../../api/alm-settings'; import { getBranches, getPullRequests } from '../../api/branches'; import { getAnalysisStatus, getTasksForComponent } from '../../api/ce'; import { getComponentData } from '../../api/components'; import { getComponentNavigation } from '../../api/nav'; +import { withAppState } from '../../components/hoc/withAppState'; import { Location, Router, withRouter } from '../../components/hoc/withRouter'; import { getBranchLikeQuery, @@ -34,9 +36,12 @@ import { } from '../../helpers/branch-like'; import { getPortfolioUrl } from '../../helpers/urls'; import { registerBranchStatus, requireAuthorization } from '../../store/rootActions'; -import { ProjectAlmBindingResponse } from '../../types/alm-settings'; +import { + ProjectAlmBindingConfigurationErrors, + ProjectAlmBindingResponse +} from '../../types/alm-settings'; import { BranchLike } from '../../types/branch-like'; -import { isPortfolioLike } from '../../types/component'; +import { ComponentQualifier, isPortfolioLike } from '../../types/component'; import { Task, TaskStatuses, TaskWarning } from '../../types/tasks'; import ComponentContainerNotFound from './ComponentContainerNotFound'; import { ComponentContext } from './ComponentContext'; @@ -44,6 +49,7 @@ import PageUnavailableDueToIndexation from './indexation/PageUnavailableDueToInd import ComponentNav from './nav/component/ComponentNav'; interface Props { + appState: Pick; children: React.ReactElement; location: Pick; registerBranchStatus: (branchLike: BranchLike, component: string, status: T.Status) => void; @@ -59,6 +65,7 @@ interface State { isPending: boolean; loading: boolean; projectBinding?: ProjectAlmBindingResponse; + projectBindingErrors?: ProjectAlmBindingConfigurationErrors; tasksInProgress?: Task[]; warnings: TaskWarning[]; } @@ -90,96 +97,85 @@ export class ComponentContainer extends React.PureComponent { window.clearTimeout(this.watchStatusTimer); } - addQualifier = (component: T.Component) => ({ - ...component, - qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier - }); - - fetchComponent() { + fetchComponent = async () => { const { branch, id: key, pullRequest } = this.props.location.query; this.setState({ loading: true }); - const onError = (response?: Response) => { + let componentWithQualifier; + try { + const [nav, { component }] = await Promise.all([ + getComponentNavigation({ component: key, branch, pullRequest }), + getComponentData({ component: key, branch, pullRequest }) + ]); + componentWithQualifier = this.addQualifier({ ...nav, ...component }); + } catch (e) { if (this.mounted) { - if (response && response.status === 403) { + if (e && e.status === HttpStatus.Forbidden) { this.props.requireAuthorization(this.props.router); } else { this.setState({ component: undefined, loading: false }); } } - }; - - Promise.all([ - getComponentNavigation({ component: key, branch, pullRequest }), - getComponentData({ component: key, branch, pullRequest }), - getProjectAlmBinding(key).catch(() => undefined) - ]) - .then(([nav, { component }, projectBinding]) => { - const componentWithQualifier = this.addQualifier({ ...nav, ...component }); - - /* - * There used to be a redirect from /dashboard to /portfolio which caused issues. - * Links should be fixed to not rely on this redirect, but: - * This is a fail-safe in case there are still some faulty links remaining. - */ - if ( - this.props.location.pathname.match('dashboard') && - isPortfolioLike(componentWithQualifier.qualifier) - ) { - this.props.router.replace(getPortfolioUrl(component.key)); - } + return; + } - if (this.mounted) { - this.setState({ projectBinding }); - } + /* + * There used to be a redirect from /dashboard to /portfolio which caused issues. + * Links should be fixed to not rely on this redirect, but: + * This is a fail-safe in case there are still some faulty links remaining. + */ + if ( + this.props.location.pathname.match('dashboard') && + isPortfolioLike(componentWithQualifier.qualifier) + ) { + this.props.router.replace(getPortfolioUrl(componentWithQualifier.key)); + } - return componentWithQualifier; - }, onError) - .then(this.fetchBranches) - .then( - ({ branchLike, branchLikes, component }) => { - if (this.mounted) { - this.setState({ - branchLike, - branchLikes, - component, - loading: false - }); - this.fetchStatus(component); - this.fetchWarnings(component, branchLike); - } - }, - () => {} - ); - } + const { branchLike, branchLikes } = await this.fetchBranches(componentWithQualifier); + + const projectBinding = await getProjectAlmBinding(key).catch(() => undefined); - fetchBranches = ( - component: T.Component - ): Promise<{ - branchLike?: BranchLike; - branchLikes: BranchLike[]; - component: T.Component; - }> => { - const breadcrumb = component.breadcrumbs.find(({ qualifier }) => { - return ['APP', 'TRK'].includes(qualifier); + if (this.mounted) { + this.setState({ + branchLike, + branchLikes, + component: componentWithQualifier, + projectBinding, + loading: false + }); + + this.fetchStatus(componentWithQualifier); + this.fetchWarnings(componentWithQualifier, branchLike); + this.fetchProjectBindingErrors(componentWithQualifier); + } + }; + + fetchBranches = async (componentWithQualifier: T.Component) => { + const breadcrumb = componentWithQualifier.breadcrumbs.find(({ qualifier }) => { + return ([ComponentQualifier.Application, ComponentQualifier.Project] as string[]).includes( + qualifier + ); }); + let branchLike = undefined; + let branchLikes: BranchLike[] = []; + if (breadcrumb) { const { key } = breadcrumb; - return Promise.all([ + const [branches, pullRequests] = await Promise.all([ getBranches(key), - breadcrumb.qualifier === 'APP' ? Promise.resolve([]) : getPullRequests(key) - ]).then(([branches, pullRequests]) => { - const branchLikes = [...branches, ...pullRequests]; - const branchLike = this.getCurrentBranchLike(branchLikes); + breadcrumb.qualifier === ComponentQualifier.Application + ? Promise.resolve([]) + : getPullRequests(key) + ]); - this.registerBranchStatuses(branchLikes, component); + branchLikes = [...branches, ...pullRequests]; + branchLike = this.getCurrentBranchLike(branchLikes); - return { branchLike, branchLikes, component }; - }); - } else { - return Promise.resolve({ branchLikes: [], component }); + this.registerBranchStatuses(branchLikes, componentWithQualifier); } + + return { branchLike, branchLikes }; }; fetchStatus = (component: T.Component) => { @@ -237,7 +233,7 @@ export class ComponentContainer extends React.PureComponent { }; fetchWarnings = (component: T.Component, branchLike?: BranchLike) => { - if (component.qualifier === 'TRK') { + if (component.qualifier === ComponentQualifier.Project) { getAnalysisStatus({ component: component.key, ...getBranchLikeQuery(branchLike) @@ -250,6 +246,22 @@ export class ComponentContainer extends React.PureComponent { } }; + fetchProjectBindingErrors = async (component: T.Component) => { + if (component.analysisDate === undefined && this.props.appState.branchesEnabled) { + const projectBindingErrors = await validateProjectAlmBinding(component.key).catch( + () => undefined + ); + if (this.mounted) { + this.setState({ projectBindingErrors }); + } + } + }; + + addQualifier = (component: T.Component) => ({ + ...component, + qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier + }); + getCurrentBranchLike = (branchLikes: BranchLike[]) => { const { query } = this.props.location; return query.pullRequest @@ -347,27 +359,32 @@ export class ComponentContainer extends React.PureComponent { currentTask, isPending, projectBinding, + projectBindingErrors, tasksInProgress } = this.state; const isInProgress = tasksInProgress && tasksInProgress.length > 0; return (
- {component && !['FIL', 'UTS'].includes(component.qualifier) && ( - - )} + {component && + !([ComponentQualifier.File, ComponentQualifier.TestFile] as string[]).includes( + component.qualifier + ) && ( + + )} {loading ? (
@@ -393,4 +410,4 @@ export class ComponentContainer extends React.PureComponent { const mapDispatchToProps = { registerBranchStatus, requireAuthorization }; -export default withRouter(connect(null, mapDispatchToProps)(ComponentContainer)); +export default withAppState(withRouter(connect(null, mapDispatchToProps)(ComponentContainer))); diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx index 57024478548..84fc4a8bb52 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx @@ -20,14 +20,15 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getProjectAlmBinding } from '../../../api/alm-settings'; +import { getProjectAlmBinding, validateProjectAlmBinding } from '../../../api/alm-settings'; import { getBranches, getPullRequests } from '../../../api/branches'; import { getAnalysisStatus, getTasksForComponent } from '../../../api/ce'; import { getComponentData } from '../../../api/components'; import { getComponentNavigation } from '../../../api/nav'; +import { mockProjectAlmBindingConfigurationErrors } from '../../../helpers/mocks/alm-settings'; import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; import { mockTask } from '../../../helpers/mocks/tasks'; -import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; +import { mockAppState, mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; import { AlmKeys } from '../../../types/alm-settings'; import { ComponentQualifier } from '../../../types/component'; import { TaskStatuses } from '../../../types/tasks'; @@ -68,7 +69,8 @@ jest.mock('../../../api/nav', () => ({ })); jest.mock('../../../api/alm-settings', () => ({ - getProjectAlmBinding: jest.fn().mockResolvedValue(undefined) + getProjectAlmBinding: jest.fn().mockResolvedValue(undefined), + validateProjectAlmBinding: jest.fn().mockResolvedValue(undefined) })); // mock this, because some of its children are using redux store @@ -312,9 +314,36 @@ it('should correctly reload last task warnings if anything got dismissed', async expect(getAnalysisStatus).toBeCalledTimes(1); }); +describe('should correctly validate the project binding depending on the context', () => { + const COMPONENT = mockComponent({ + breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.Project }] + }); + const PROJECT_BINDING_ERRORS = mockProjectAlmBindingConfigurationErrors(); + + it.each([ + ["has an analysis; won't perform any check", { ...COMPONENT, analysisDate: '2020-01' }], + ['has a project binding; check is OK', COMPONENT, undefined, 1], + ['has a project binding; check is not OK', COMPONENT, PROJECT_BINDING_ERRORS, 1] + ])('%s', async (_, component, projectBindingErrors = undefined, n = 0) => { + (getComponentNavigation as jest.Mock).mockResolvedValueOnce({}); + (getComponentData as jest.Mock).mockResolvedValueOnce({ component }); + + if (n > 0) { + (validateProjectAlmBinding as jest.Mock).mockResolvedValueOnce(projectBindingErrors); + } + + const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) }); + await waitAndUpdate(wrapper); + expect(wrapper.state().projectBindingErrors).toBe(projectBindingErrors); + + expect(validateProjectAlmBinding).toBeCalledTimes(n); + }); +}); + function shallowRender(props: Partial = {}) { return shallow( ) => void; onWarningDismiss: () => void; projectBinding?: ProjectAlmBindingResponse; + projectBindingErrors?: ProjectAlmBindingConfigurationErrors; warnings: TaskWarning[]; } +const ALERT_HEIGHT = 30; + export default function ComponentNav(props: ComponentNavProps) { const { branchLikes, @@ -57,6 +64,7 @@ export default function ComponentNav(props: ComponentNavProps) { isInProgress, isPending, projectBinding, + projectBindingErrors, warnings } = props; const { contextNavHeightRaw, globalNavHeightRaw } = rawSizes; @@ -78,9 +86,11 @@ export default function ComponentNav(props: ComponentNavProps) { } }, [component, component.key]); - let notifComponent; + let contextNavHeight = contextNavHeightRaw; + + let bgTaskNotifComponent; if (isInProgress || isPending || (currentTask && currentTask.status === TaskStatuses.Failed)) { - notifComponent = ( + bgTaskNotifComponent = ( ); + contextNavHeight += ALERT_HEIGHT; } - const contextNavHeight = notifComponent ? contextNavHeightRaw + 30 : contextNavHeightRaw; + let prDecoNotifComponent; + if (projectBindingErrors !== undefined) { + prDecoNotifComponent = ( + + ); + contextNavHeight += ALERT_HEIGHT; + } return ( - + + {bgTaskNotifComponent} + {prDecoNotifComponent} + + }>
{ } return ( - + {message} ); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx new file mode 100644 index 00000000000..1156c729560 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 } from 'sonar-ui-common/helpers/l10n'; +import { + ALM_INTEGRATION, + PULL_REQUEST_DECORATION_BINDING_CATEGORY +} from '../../../../apps/settings/components/AdditionalCategoryKeys'; +import { withCurrentUser } from '../../../../components/hoc/withCurrentUser'; +import { hasGlobalPermission } from '../../../../helpers/users'; +import { + AlmKeys, + ProjectAlmBindingConfigurationErrors, + ProjectAlmBindingConfigurationErrorScope +} from '../../../../types/alm-settings'; +import { Permissions } from '../../../../types/permissions'; + +export interface ComponentNavProjectBindingErrorNotifProps { + alm?: AlmKeys; + component: T.Component; + currentUser: T.CurrentUser; + projectBindingErrors: ProjectAlmBindingConfigurationErrors; +} + +export function ComponentNavProjectBindingErrorNotif( + props: ComponentNavProjectBindingErrorNotifProps +) { + const { alm, component, currentUser, projectBindingErrors } = props; + const isSysadmin = hasGlobalPermission(currentUser, Permissions.Admin); + + let action; + if (projectBindingErrors.scope === ProjectAlmBindingConfigurationErrorScope.Global) { + if (isSysadmin) { + action = ( + + {translate('component_navigation.pr_deco.action.check_global_settings')} + + ); + } else { + action = translate('component_navigation.pr_deco.action.contact_sys_admin'); + } + } else if (projectBindingErrors.scope === ProjectAlmBindingConfigurationErrorScope.Project) { + if (component.configuration?.showSettings) { + action = ( + + {translate('component_navigation.pr_deco.action.check_project_settings')} + + ); + } else { + action = translate('component_navigation.pr_deco.action.contact_project_admin'); + } + } + + return ( + + + + ); +} + +export default withCurrentUser(ComponentNavProjectBindingErrorNotif); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx index 7053107e433..47d8fbc25dc 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx @@ -19,6 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockProjectAlmBindingConfigurationErrors } from '../../../../../helpers/mocks/alm-settings'; import { mockTask, mockTaskWarning } from '../../../../../helpers/mocks/tasks'; import { mockComponent } from '../../../../../helpers/testMocks'; import { ComponentQualifier } from '../../../../../types/component'; @@ -41,6 +42,11 @@ it('renders correctly', () => { expect(shallowRender({ currentTask: mockTask({ status: TaskStatuses.Failed }) })).toMatchSnapshot( 'has failed notification' ); + expect( + shallowRender({ + projectBindingErrors: mockProjectAlmBindingConfigurationErrors() + }) + ).toMatchSnapshot('has failed project binding'); }); it('correctly adds data to the history if there are breadcrumbs', () => { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavProjectBindingErrorNotif-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavProjectBindingErrorNotif-test.tsx new file mode 100644 index 00000000000..29a2feb410f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavProjectBindingErrorNotif-test.tsx @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { mockProjectAlmBindingConfigurationErrors } from '../../../../../helpers/mocks/alm-settings'; +import { mockComponent, mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks'; +import { + AlmKeys, + ProjectAlmBindingConfigurationErrorScope +} from '../../../../../types/alm-settings'; +import { Permissions } from '../../../../../types/permissions'; +import { + ComponentNavProjectBindingErrorNotif, + ComponentNavProjectBindingErrorNotifProps +} from '../ComponentNavProjectBindingErrorNotif'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('global, no admin'); + expect( + shallowRender({ + currentUser: mockLoggedInUser({ permissions: { global: [Permissions.Admin] } }) + }) + ).toMatchSnapshot('global, admin'); + expect( + shallowRender({ + projectBindingErrors: mockProjectAlmBindingConfigurationErrors({ + scope: ProjectAlmBindingConfigurationErrorScope.Project + }) + }) + ).toMatchSnapshot('project, no admin'); + expect( + shallowRender({ + component: mockComponent({ configuration: { showSettings: true } }), + projectBindingErrors: mockProjectAlmBindingConfigurationErrors({ + scope: ProjectAlmBindingConfigurationErrorScope.Project + }) + }) + ).toMatchSnapshot('project, admin'); + expect( + shallowRender({ + projectBindingErrors: mockProjectAlmBindingConfigurationErrors({ + scope: ProjectAlmBindingConfigurationErrorScope.Unknown + }) + }) + ).toMatchSnapshot('unknown'); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap index a5669ebe18d..ca190a5ec8c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap @@ -4,6 +4,7 @@ exports[`renders correctly: default 1`] = ` } >
+ + + } +> +
+ + +
+ + + + + +`; + +exports[`renders correctly: has failed project binding 1`] = ` + + + } >
+ + isInProgress={true} + isPending={false} + /> + } >
+ + isInProgress={false} + isPending={true} + /> + } >
} >
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap new file mode 100644 index 00000000000..cbf60122bc6 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavProjectBindingErrorNotif-test.tsx.snap @@ -0,0 +1,114 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: global, admin 1`] = ` + + + component_navigation.pr_deco.action.check_global_settings + , + } + } + /> + +`; + +exports[`should render correctly: global, no admin 1`] = ` + + + +`; + +exports[`should render correctly: project, admin 1`] = ` + + + component_navigation.pr_deco.action.check_project_settings + , + } + } + /> + +`; + +exports[`should render correctly: project, no admin 1`] = ` + + + +`; + +exports[`should render correctly: unknown 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index ae61dfde85c..3e5adbeecdf 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -69,6 +69,10 @@ th.hide-overflow { margin-top: 0 !important; } +.null-spacer-bottom { + margin-bottom: 0 !important; +} + .spacer { margin: 8px !important; } diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx index 48f82838adf..f20850762b6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx @@ -33,15 +33,11 @@ import { } from '../../../../../api/alm-settings'; import { mockAlmSettingsInstance, + mockProjectAlmBindingConfigurationErrors, mockProjectAlmBindingResponse } from '../../../../../helpers/mocks/alm-settings'; import { mockComponent, mockCurrentUser } from '../../../../../helpers/testMocks'; -import { - AlmKeys, - AlmSettingsInstance, - ProjectAlmBindingConfigurationErrors, - ProjectAlmBindingConfigurationErrorScope -} from '../../../../../types/alm-settings'; +import { AlmKeys, AlmSettingsInstance } from '../../../../../types/alm-settings'; import { PRDecorationBinding } from '../PRDecorationBinding'; import PRDecorationBindingRenderer from '../PRDecorationBindingRenderer'; @@ -373,10 +369,7 @@ it('should call the validation WS and store errors', async () => { mockProjectAlmBindingResponse({ key: 'key' }) ); - const errors: ProjectAlmBindingConfigurationErrors = { - scope: ProjectAlmBindingConfigurationErrorScope.Global, - errors: [{ msg: 'Test' }, { msg: 'tesT' }] - }; + const errors = mockProjectAlmBindingConfigurationErrors(); (validateProjectAlmBinding as jest.Mock).mockRejectedValueOnce(errors); const wrapper = shallowRender(); diff --git a/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts b/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts index a5cb9c7868b..0abd3e4ef53 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/alm-settings.ts @@ -27,6 +27,8 @@ import { BitbucketCloudBindingDefinition, GithubBindingDefinition, GitlabBindingDefinition, + ProjectAlmBindingConfigurationErrors, + ProjectAlmBindingConfigurationErrorScope, ProjectAlmBindingResponse, ProjectAzureBindingResponse, ProjectBitbucketBindingResponse, @@ -198,3 +200,13 @@ export function mockAlmSettingsBindingStatus( ...overrides }; } + +export function mockProjectAlmBindingConfigurationErrors( + overrides: Partial = {} +): ProjectAlmBindingConfigurationErrors { + return { + scope: ProjectAlmBindingConfigurationErrorScope.Global, + errors: [{ msg: 'Foo bar is not correct' }, { msg: 'Bar baz has no permissions here' }], + ...overrides + }; +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 85149fff2d7..bd9bcaff9a7 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2802,6 +2802,11 @@ component_navigation.status.last_blocked_due_to_bad_license_X=Last analysis bloc component_navigation.last_analysis_had_warnings=Last analysis had {warnings} component_navigation.x_warnings={warningsCount} {warningsCount, plural, one {warning} other {warnings}} +component_navigation.pr_deco.error_detected_X=We've detected an issue with your configuration. Your SonarQube instance won't be able to perform any pull request decoration. {action} +component_navigation.pr_deco.action.check_global_settings=Please check your global settings. +component_navigation.pr_deco.action.contact_sys_admin=Please contact your system administrator. +component_navigation.pr_deco.action.check_project_settings=Please check your project settings. +component_navigation.pr_deco.action.contact_project_admin=Please contact your project administrator. background_task.status.ALL=All background_task.status.PENDING=Pending