From 58ef20e45102167223529f7df26f4434e8039045 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 30 Aug 2018 12:19:33 +0200 Subject: [PATCH] SONAR-11207 Display analysis warnings in the web app --- server/sonar-web/src/main/js/api/ce.ts | 2 +- .../components/nav/component/ComponentNav.tsx | 1 + .../nav/component/ComponentNavMeta.tsx | 9 +- .../nav/component/ComponentNavWarnings.tsx | 78 +++++++++++++ .../__tests__/ComponentNavWarnings-test.tsx | 28 +++++ .../ComponentNavWarnings-test.tsx.snap | 31 ++++++ .../main/js/app/styles/components/badges.css | 6 + server/sonar-web/src/main/js/app/theme.js | 3 + server/sonar-web/src/main/js/app/types.ts | 4 + .../components/TaskActions.tsx | 41 ++++++- .../components/__tests__/TaskActions-test.tsx | 13 ++- .../__snapshots__/TaskActions-test.tsx.snap | 9 ++ .../common/AnalysisWarningsModal.tsx | 103 ++++++++++++++++++ .../__tests__/AnalysisWarningsModal-test.tsx | 35 ++++++ .../AnalysisWarningsModal-test.tsx.snap | 61 +++++++++++ .../icons-components/WarningIcon.tsx | 33 ++++++ .../resources/org/sonar/l10n/core.properties | 5 + 17 files changed, 454 insertions(+), 8 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx create mode 100644 server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx create mode 100644 server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx diff --git a/server/sonar-web/src/main/js/api/ce.ts b/server/sonar-web/src/main/js/api/ce.ts index b72d148873e..3d61fbc6589 100644 --- a/server/sonar-web/src/main/js/api/ce.ts +++ b/server/sonar-web/src/main/js/api/ce.ts @@ -33,7 +33,7 @@ export function getStatus(componentId?: string): Promise { return getJSON('/api/ce/activity_status', data); } -export function getTask(id: string, additionalFields?: string[]): Promise { +export function getTask(id: string, additionalFields?: string[]): Promise { return getJSON('/api/ce/task', { id, additionalFields }).then(r => r.task); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx index b99e3f10d61..46ecd76a630 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx @@ -96,6 +96,7 @@ export default class ComponentNav extends React.PureComponent { branchLike={currentBranchLike} branchMeasures={this.props.branchMeasures} component={component} + currentTask={currentTask} /> + {currentTask && + Boolean(currentTask.warningCount) && } {component.analysisDate && (
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx new file mode 100644 index 00000000000..192d562afd7 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { lazyLoad } from '../../../../components/lazyLoad'; +import WarningIcon from '../../../../components/icons-components/WarningIcon'; +import { Task } from '../../../types'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; + +const AnalysisWarningsModal = lazyLoad(() => + import('../../../../components/common/AnalysisWarningsModal') +); + +interface Props { + task: Pick; +} + +interface State { + modal: boolean; +} + +export default class ComponentNavWarnings extends React.PureComponent { + state: State = { modal: false }; + + handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.setState({ modal: true }); + }; + + handleCloseModal = () => { + this.setState({ modal: false }); + }; + + render() { + return ( + <> +
+ + + {translateWithParameters( + 'component_navigation.x_warnings', + String(this.props.task.warningCount) + )} + + ) + }} + /> +
+ {this.state.modal && ( + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx new file mode 100644 index 00000000000..9eba3b71968 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import ComponentNavWarnings from '../ComponentNavWarnings'; + +it('should render', () => { + const wrapper = shallow(); + wrapper.setState({ modal: true }); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap new file mode 100644 index 00000000000..1f672b50096 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` + +
+ + + component_navigation.x_warnings.undefined + , + } + } + /> +
+ +
+`; diff --git a/server/sonar-web/src/main/js/app/styles/components/badges.css b/server/sonar-web/src/main/js/app/styles/components/badges.css index 05d1f81238f..8ec56d49e69 100644 --- a/server/sonar-web/src/main/js/app/styles/components/badges.css +++ b/server/sonar-web/src/main/js/app/styles/components/badges.css @@ -53,6 +53,12 @@ a.badge { border-radius: 50px; } +.badge-medium { + height: var(--controlHeight); + line-height: calc(var(--controlHeight)); + letter-spacing: 0.01em; +} + .list-group-item > .badge, .list-group-item-heading > .badge { float: right; diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index bc1731dd834..ccaff6a1bf0 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -56,6 +56,9 @@ module.exports = { snippetFontColor: '#f0f0f0', + // alerts + warningIconColor: '#e2bf41', + // sizes grid, gridSize: `${grid}px`, diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 6d6965e1fce..109e08c56ed 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -728,6 +728,7 @@ export interface Task { componentName?: string; componentQualifier?: string; errorMessage?: string; + errorStacktrace?: string; errorType?: string; executedAt?: string; executionTimeMs?: number; @@ -738,11 +739,14 @@ export interface Task { organization: string; pullRequest?: string; pullRequestTitle?: string; + scannerContext?: string; startedAt?: string; status: string; submittedAt: string; submitterLogin?: string; type: string; + warningCount?: number; + warnings?: string[]; } export interface TestCase { diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx index 68587cd0ad9..119d8ac7d61 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx @@ -24,6 +24,12 @@ import { STATUSES } from '../constants'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown'; import { Task } from '../../../app/types'; +import { lazyLoad } from '../../../components/lazyLoad'; + +const AnalysisWarningsModal = lazyLoad( + () => import('../../../components/common/AnalysisWarningsModal'), + 'AnalysisWarningsModal' +); interface Props { component?: {}; @@ -35,12 +41,14 @@ interface Props { interface State { scannerContextOpen: boolean; stacktraceOpen: boolean; + warningsOpen: boolean; } export default class TaskActions extends React.PureComponent { state: State = { scannerContextOpen: false, - stacktraceOpen: false + stacktraceOpen: false, + warningsOpen: false }; handleFilterClick = () => { @@ -55,13 +63,25 @@ export default class TaskActions extends React.PureComponent { this.setState({ scannerContextOpen: true }); }; - closeScannerContext = () => this.setState({ scannerContextOpen: false }); + closeScannerContext = () => { + this.setState({ scannerContextOpen: false }); + }; handleShowStacktraceClick = () => { this.setState({ stacktraceOpen: true }); }; - closeStacktrace = () => this.setState({ stacktraceOpen: false }); + closeStacktrace = () => { + this.setState({ stacktraceOpen: false }); + }; + + handleShowWarningsClick = () => { + this.setState({ warningsOpen: true }); + }; + + closeWarnings = () => { + this.setState({ warningsOpen: false }); + }; render() { const { component, task } = this.props; @@ -69,7 +89,9 @@ export default class TaskActions extends React.PureComponent { const canFilter = component === undefined; const canCancel = task.status === STATUSES.PENDING; const canShowStacktrace = task.errorMessage !== undefined; - const hasActions = canFilter || canCancel || task.hasScannerContext || canShowStacktrace; + const canShowWarnings = task.warningCount !== undefined && task.warningCount > 0; + const hasActions = + canFilter || canCancel || task.hasScannerContext || canShowStacktrace || canShowWarnings; if (!hasActions) { return  ; @@ -109,6 +131,13 @@ export default class TaskActions extends React.PureComponent { {translate('background_tasks.show_stacktrace')} )} + {canShowWarnings && ( + + {translate('background_tasks.show_warnings')} + + )} {this.state.scannerContextOpen && ( @@ -116,6 +145,10 @@ export default class TaskActions extends React.PureComponent { )} {this.state.stacktraceOpen && } + + {this.state.warningsOpen && ( + + )} ); } diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx index 456f1f0e07a..5ce2e4bcf58 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import TaskActions from '../TaskActions'; import { click } from '../../../../helpers/testUtils'; +import { Task } from '../../../../app/types'; it('renders', () => { expect(shallowRender()).toMatchSnapshot(); @@ -48,7 +49,16 @@ it('shows scanner context', () => { expect(wrapper.find('ScannerContext').exists()).toBeFalsy(); }); -function shallowRender(fields?: any, props?: any) { +it('shows warnings', () => { + const wrapper = shallowRender({ warningCount: 2 }); + click(wrapper.find('.js-task-show-warnings')); + expect(wrapper.find('AnalysisWarningsModal')).toMatchSnapshot(); + wrapper.find('AnalysisWarningsModal').prop('onClose')(); + wrapper.update(); + expect(wrapper.find('AnalysisWarningsModal').exists()).toBeFalsy(); +}); + +function shallowRender(fields?: Partial, props?: Partial) { return shallow( `; + +exports[`shows warnings 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx b/server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx new file mode 100644 index 00000000000..ec73d16e563 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 DeferredSpinner from './DeferredSpinner'; +import Modal from '../controls/Modal'; +import { ResetButtonLink } from '../ui/buttons'; +import { translate } from '../../helpers/l10n'; +import WarningIcon from '../icons-components/WarningIcon'; +import { getTask } from '../../api/ce'; + +interface Props { + onClose: () => void; + taskId: string; +} + +interface State { + loading: boolean; + warnings: string[]; +} + +export default class AnalysisWarningsModal extends React.PureComponent { + mounted = false; + state: State = { + loading: true, + warnings: [] + }; + + componentDidMount() { + this.mounted = true; + this.loadWarnings(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.taskId !== this.props.taskId) { + this.loadWarnings(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + loadWarnings() { + this.setState({ loading: true }); + getTask(this.props.taskId, ['warnings']).then( + ({ warnings = [] }) => { + if (this.mounted) { + this.setState({ loading: false, warnings }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + + render() { + const header = translate('warnings'); + return ( + +
+

{header}

+
+ +
+ + {this.state.warnings.map((warning, index) => ( +
+ +
{warning}
+
+ ))} +
+
+ +
+ + {translate('close')} + +
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx new file mode 100644 index 00000000000..4d833e396f7 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import AnalysisWarningsModal from '../AnalysisWarningsModal'; +import { waitAndUpdate } from '../../../helpers/testUtils'; +import { getTask } from '../../../api/ce'; + +jest.mock('../../../api/ce', () => ({ + getTask: jest.fn().mockResolvedValue({ warnings: ['message foo', 'message-bar'] }) +})); + +it('should fetch warnings and render', async () => { + const wrapper = shallow(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + expect(getTask).toBeCalledWith('abcd1234', ['warnings']); +}); diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap new file mode 100644 index 00000000000..a5823a94881 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should fetch warnings and render 1`] = ` + +
+

+ warnings +

+
+
+ +
+ +
+ message foo +
+
+
+ +
+ message-bar +
+
+
+
+
+ + close + +
+
+`; diff --git a/server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx new file mode 100644 index 00000000000..eb9616ab2b6 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 Icon, { IconProps } from './Icon'; +import * as theme from '../../app/theme'; + +export default function WarningIcon({ className, fill = theme.warningIconColor, size }: IconProps) { + return ( + + + + ); +} 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 24d6a527259..12799a9c9e0 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -189,6 +189,7 @@ view=View views=Views violations=Violations visibility=Visibility +warnings=Warnings with=With worst=Worst yes=Yes @@ -2262,6 +2263,9 @@ component_navigation.status.in_progress=The analysis is in progress. component_navigation.status.in_progress.admin=The analysis is in progress. More details available on the {url} page. component_navigation.status.last_blocked_due_to_bad_license=Last analysis blocked due to an invalid license, which has since been corrected. Please reanalyze this project. +component_navigation.last_analsys_had_warnings=Last analysis had {warnings} +component_navigation.x_warnings={0} warnings + background_task.status.ALL=All background_task.status.PENDING=Pending background_task.status.IN_PROGRESS=In Progress @@ -2300,6 +2304,7 @@ background_tasks.cancel_all_tasks=Cancel All Pending Tasks background_tasks.scanner_context=Scanner Context background_tasks.show_scanner_context=Show Scanner Context background_tasks.show_stacktrace=Show Error Details +background_tasks.show_warnings=Show Warnings background_tasks.error_message=Error Message background_tasks.error_stacktrace=Error Details background_tasks.pending=pending -- 2.39.5