]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11207 Display analysis warnings in the web app
authorStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 30 Aug 2018 10:19:33 +0000 (12:19 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 10 Oct 2018 07:23:03 +0000 (09:23 +0200)
17 files changed:
server/sonar-web/src/main/js/api/ce.ts
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavWarnings.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavWarnings-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavWarnings-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/app/styles/components/badges.css
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/TaskActions-test.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/TaskActions-test.tsx.snap
server/sonar-web/src/main/js/components/common/AnalysisWarningsModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/__tests__/AnalysisWarningsModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/AnalysisWarningsModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/WarningIcon.tsx [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b72d148873e23e91f9c76b665ec4eb297d62def4..3d61fbc6589287dc2c7fece3643eb3273212a02e 100644 (file)
@@ -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);
 }
 
index b99e3f10d6120ba3a7d2e8126c4af96e00887e7f..46ecd76a630e813fb4c989498b72732920f0d8d8 100644 (file)
@@ -96,6 +96,7 @@ export default class ComponentNav extends React.PureComponent<Props> {
             branchLike={currentBranchLike}
             branchMeasures={this.props.branchMeasures}
             component={component}
+            currentTask={currentTask}
           />
         </div>
         <ComponentNavMenu
index a1e503abd3dbf61b70000aceacba7147b064ca3f..f6c5487409ce38d3db1012cc7ff2aec2530eb8b3 100644 (file)
@@ -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 (file)
index 0000000..192d562
--- /dev/null
@@ -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 (file)
index 0000000..9eba3b7
--- /dev/null
@@ -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 (file)
index 0000000..1f672b5
--- /dev/null
@@ -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>
+`;
index 05d1f81238f26f5ae67549c5817093a5a268271f..8ec56d49e69da66442aa0d199de273d4d65f88ef 100644 (file)
@@ -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;
index bc1731dd83470cf5f59f654c0d542a75af62848c..ccaff6a1bf0d93f935b5c6a920d3a5d92cb6e5d2 100644 (file)
@@ -56,6 +56,9 @@ module.exports = {
 
   snippetFontColor: '#f0f0f0',
 
+  // alerts
+  warningIconColor: '#e2bf41',
+
   // sizes
   grid,
   gridSize: `${grid}px`,
index 6d6965e1fce93136ac666ad4fc148f8885c542f1..109e08c56edbb160332bebd1b03f35170d18403c 100644 (file)
@@ -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 {
index 68587cd0ad9748ebea4ce46e52e9e7794262f856..119d8ac7d61f66cb56be431eb8fc37dc149bf812 100644 (file)
@@ -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>
     );
   }
index 456f1f0e07a6d9643ad334d4c42bc5522a9a504f..5ce2e4bcf58a98a6fffb97b7a37e29f838711ece 100644 (file)
@@ -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
index 84a4e16e597ac914d7cb0fe6cf00c3c44ff524b1..0d460cfddc07a337b1da00309ef0f7416d2db60b 100644 (file)
@@ -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 (file)
index 0000000..ec73d16
--- /dev/null
@@ -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 (file)
index 0000000..4d833e3
--- /dev/null
@@ -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 (file)
index 0000000..a5823a9
--- /dev/null
@@ -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 (file)
index 0000000..eb9616a
--- /dev/null
@@ -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>
+  );
+}
index 24d6a527259bbf9d63f65bfbde84f012b7b3cefb..12799a9c9e002c01467f562d56bb413e99044607 100644 (file)
@@ -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