aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-08-30 12:19:33 +0200
committersonartech <sonartech@sonarsource.com>2018-10-10 09:23:03 +0200
commit58ef20e45102167223529f7df26f4434e8039045 (patch)
treec512ee2b76b847f8af543a1c0a3a9146b1c7a676 /server/sonar-web/src/main/js
parent6a08bacfe5b6d692e83e493246efad11f22ff70e (diff)
downloadsonarqube-58ef20e45102167223529f7df26f4434e8039045.tar.gz
sonarqube-58ef20e45102167223529f7df26f4434e8039045.zip
SONAR-11207 Display analysis warnings in the web app
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/ce.ts2
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx1
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx9
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx78
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap31
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/badges.css6
-rw-r--r--server/sonar-web/src/main/js/app/theme.js3
-rw-r--r--server/sonar-web/src/main/js/app/types.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx103
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx35
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap61
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx33
16 files changed, 449 insertions, 8 deletions
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<any> {
return getJSON('/api/ce/activity_status', data);
}
-export function getTask(id: string, additionalFields?: string[]): Promise<any> {
+export function getTask(id: string, additionalFields?: string[]): Promise<Task> {
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<Props> {
branchLike={currentBranchLike}
branchMeasures={this.props.branchMeasures}
component={component}
+ currentTask={currentTask}
/>
</div>
<ComponentNavMenu
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
index a1e503abd3d..f6c5487409c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import { connect } from 'react-redux';
+import ComponentNavWarnings from './ComponentNavWarnings';
import {
BranchLike,
Component,
@@ -26,7 +27,8 @@ import {
isLoggedIn,
HomePageType,
HomePage,
- Measure
+ Measure,
+ Task
} from '../../../types';
import BranchMeasures from '../../../../components/common/BranchMeasures';
import BranchStatus from '../../../../components/common/BranchStatus';
@@ -52,9 +54,10 @@ interface Props extends StateProps {
branchLike?: BranchLike;
branchMeasures?: Measure[];
component: Component;
+ currentTask?: Task;
}
-export function ComponentNavMeta({ branchLike, branchMeasures, component, currentUser }: Props) {
+export function ComponentNavMeta({ branchLike, branchMeasures, component, currentTask, currentUser }: Props) {
const mainBranch = !branchLike || isMainBranch(branchLike);
const longBranch = isLongLivingBranch(branchLike);
const currentPage = getCurrentPage(component, branchLike);
@@ -62,6 +65,8 @@ export function ComponentNavMeta({ branchLike, branchMeasures, component, curren
return (
<div className="navbar-context-meta">
+ {currentTask &&
+ Boolean(currentTask.warningCount) && <ComponentNavWarnings task={currentTask} />}
{component.analysisDate && (
<div className="spacer-left text-ellipsis">
<DateTimeFormatter date={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<Task, 'id' | 'warningCount'>;
+}
+
+interface State {
+ modal: boolean;
+}
+
+export default class ComponentNavWarnings extends React.PureComponent<Props, State> {
+ state: State = { modal: false };
+
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState({ modal: true });
+ };
+
+ handleCloseModal = () => {
+ this.setState({ modal: false });
+ };
+
+ render() {
+ return (
+ <>
+ <div className="badge badge-focus badge-medium display-inline-flex-center js-component-analysis-warnings">
+ <WarningIcon className="spacer-right" />
+ <FormattedMessage
+ defaultMessage={translate('component_navigation.last_analsys_had_warnings')}
+ id="component_navigation.last_analsys_had_warnings"
+ values={{
+ warnings: (
+ <a href="#" onClick={this.handleClick}>
+ {translateWithParameters(
+ 'component_navigation.x_warnings',
+ String(this.props.task.warningCount)
+ )}
+ </a>
+ )
+ }}
+ />
+ </div>
+ {this.state.modal && (
+ <AnalysisWarningsModal onClose={this.handleCloseModal} taskId={this.props.task.id} />
+ )}
+ </>
+ );
+ }
+}
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(<ComponentNavWarnings task={{ id: 'abcd1234' }} />);
+ 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`] = `
+<React.Fragment>
+ <div
+ className="badge badge-focus badge-medium display-inline-flex-center js-component-analysis-warnings"
+ >
+ <WarningIcon
+ className="spacer-right"
+ />
+ <FormattedMessage
+ defaultMessage="component_navigation.last_analsys_had_warnings"
+ id="component_navigation.last_analsys_had_warnings"
+ values={
+ Object {
+ "warnings": <a
+ href="#"
+ onClick={[Function]}
+ >
+ component_navigation.x_warnings.undefined
+ </a>,
+ }
+ }
+ />
+ </div>
+ <LazyLoader
+ onClose={[Function]}
+ taskId="abcd1234"
+ />
+</React.Fragment>
+`;
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<Props, State> {
state: State = {
scannerContextOpen: false,
- stacktraceOpen: false
+ stacktraceOpen: false,
+ warningsOpen: false
};
handleFilterClick = () => {
@@ -55,13 +63,25 @@ export default class TaskActions extends React.PureComponent<Props, State> {
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<Props, State> {
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 <td>&nbsp;</td>;
@@ -109,6 +131,13 @@ export default class TaskActions extends React.PureComponent<Props, State> {
{translate('background_tasks.show_stacktrace')}
</ActionsDropdownItem>
)}
+ {canShowWarnings && (
+ <ActionsDropdownItem
+ className="js-task-show-warnings"
+ onClick={this.handleShowWarningsClick}>
+ {translate('background_tasks.show_warnings')}
+ </ActionsDropdownItem>
+ )}
</ActionsDropdown>
{this.state.scannerContextOpen && (
@@ -116,6 +145,10 @@ export default class TaskActions extends React.PureComponent<Props, State> {
)}
{this.state.stacktraceOpen && <Stacktrace onClose={this.closeStacktrace} task={task} />}
+
+ {this.state.warningsOpen && (
+ <AnalysisWarningsModal onClose={this.closeWarnings} taskId={task.id} />
+ )}
</td>
);
}
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<Function>('onClose')();
+ wrapper.update();
+ expect(wrapper.find('AnalysisWarningsModal').exists()).toBeFalsy();
+});
+
+function shallowRender(fields?: Partial<Task>, props?: Partial<TaskActions['props']>) {
return shallow(
<TaskActions
onCancelTask={jest.fn()}
@@ -57,6 +67,7 @@ function shallowRender(fields?: any, props?: any) {
componentName: 'foo',
status: 'PENDING',
id: '123',
+ organization: 'org',
submittedAt: '2017-01-01',
type: 'REPORT',
...fields
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap
index 84a4e16e597..0d460cfddc0 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap
@@ -127,6 +127,7 @@ exports[`shows scanner context 1`] = `
"componentName": "foo",
"hasScannerContext": true,
"id": "123",
+ "organization": "org",
"status": "PENDING",
"submittedAt": "2017-01-01",
"type": "REPORT",
@@ -143,6 +144,7 @@ exports[`shows stack trace 1`] = `
"componentName": "foo",
"errorMessage": "error!",
"id": "123",
+ "organization": "org",
"status": "PENDING",
"submittedAt": "2017-01-01",
"type": "REPORT",
@@ -150,3 +152,10 @@ exports[`shows stack trace 1`] = `
}
/>
`;
+
+exports[`shows warnings 1`] = `
+<AnalysisWarningsModal
+ onClose={[Function]}
+ taskId="123"
+/>
+`;
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<Props, State> {
+ 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 (
+ <Modal contentLabel={header} onRequestClose={this.props.onClose}>
+ <header className="modal-head">
+ <h2>{header}</h2>
+ </header>
+
+ <div className="modal-body js-analysis-warnings">
+ <DeferredSpinner loading={this.state.loading}>
+ {this.state.warnings.map((warning, index) => (
+ <div className="panel panel-vertical" key={index}>
+ <WarningIcon className="pull-left spacer-right" />
+ <div className="overflow-hidden markdown">{warning}</div>
+ </div>
+ ))}
+ </DeferredSpinner>
+ </div>
+
+ <footer className="modal-foot">
+ <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
+ {translate('close')}
+ </ResetButtonLink>
+ </footer>
+ </Modal>
+ );
+ }
+}
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(<AnalysisWarningsModal onClose={jest.fn()} taskId="abcd1234" />);
+ 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`] = `
+<Modal
+ contentLabel="warnings"
+ onRequestClose={[MockFunction]}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ warnings
+ </h2>
+ </header>
+ <div
+ className="modal-body js-analysis-warnings"
+ >
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ >
+ <div
+ className="panel panel-vertical"
+ key="0"
+ >
+ <WarningIcon
+ className="pull-left spacer-right"
+ />
+ <div
+ className="overflow-hidden markdown"
+ >
+ message foo
+ </div>
+ </div>
+ <div
+ className="panel panel-vertical"
+ key="1"
+ >
+ <WarningIcon
+ className="pull-left spacer-right"
+ />
+ <div
+ className="overflow-hidden markdown"
+ >
+ message-bar
+ </div>
+ </div>
+ </DeferredSpinner>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <ResetButtonLink
+ className="js-modal-close"
+ onClick={[MockFunction]}
+ >
+ close
+ </ResetButtonLink>
+ </footer>
+</Modal>
+`;
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 (
+ <Icon className={className} size={size}>
+ <path
+ d="M9 12.242v-1.484c0-.14-.11-.258-.25-.258h-1.5c-.14 0-.25.117-.25.258v1.484c0 .14.11.258.25.258h1.5c.14 0 .25-.117.25-.258zM8.984 9.32l.141-3.586a.189.189 0 0 0-.078-.148C9 5.546 8.93 5.5 8.859 5.5H7.141c-.07 0-.141.047-.188.086-.055.039-.078.117-.078.164l.133 3.57c0 .102.117.18.265.18H8.72c.14 0 .258-.078.265-.18zm-.109-7.297l6 11A1 1 0 0 1 14 14.5H2a1 1 0 0 1-.875-1.477l6-11a.994.994 0 0 1 1.75 0z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}