]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10264 Create the project badges page for SonarCloud
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 10 Jan 2018 16:51:19 +0000 (17:51 +0100)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 25 Jan 2018 14:16:50 +0000 (15:16 +0100)
- SONAR-10268 Create the badges page with the Scanned on SonarCloud badge
- SONAR-10272 Allow user to choose the color of the badge
- SONAR-10268 Add the "Scanned on SonarCloud" svg files
- SONAR-10264 SONAR-10271 Add standard measure badges
- SONAR-10264 Allow user to choose a metric for the standard badge

27 files changed:
server/sonar-web/src/main/js/api/web-api.ts
server/sonar-web/src/main/js/app/styles/init/misc.css
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/styles.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/badges/utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/meta/Meta.js
server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css
server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
server/sonar-web/src/main/js/components/SelectList/styles.css
server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx
server/sonar-web/src/main/js/components/workspace/styles.css
server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg [new file with mode: 0644]
server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg [new file with mode: 0644]
server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 7004d4dc8ff02209074a7d1f52a2eaf99deb0e3b..dab190049a0eb734493fbf7fa3053e63356cd2bb 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { getJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
 
 export interface Changelog {
   description: string;
@@ -65,16 +66,20 @@ export interface Example {
   format: string;
 }
 
-export function fetchWebApi(showInternal: boolean = true): Promise<Array<Domain>> {
-  return getJSON('/api/webservices/list', { include_internals: showInternal }).then(r =>
-    r.webServices.map((domain: any) => {
-      const deprecated = !domain.actions.find((action: any) => !action.deprecatedSince);
-      const internal = !domain.actions.find((action: any) => !action.internal);
-      return { ...domain, deprecated, internal };
-    })
-  );
+export function fetchWebApi(showInternal = true): Promise<Domain[]> {
+  return getJSON('/api/webservices/list', { include_internals: showInternal })
+    .then(r =>
+      r.webServices.map((domain: any) => {
+        const deprecated = !domain.actions.find((action: any) => !action.deprecatedSince);
+        const internal = !domain.actions.find((action: any) => !action.internal);
+        return { ...domain, deprecated, internal };
+      })
+    )
+    .catch(throwGlobalError);
 }
 
 export function fetchResponseExample(domain: string, action: string): Promise<Example> {
-  return getJSON('/api/webservices/response_example', { controller: domain, action });
+  return getJSON('/api/webservices/response_example', { controller: domain, action }).catch(
+    throwGlobalError
+  );
 }
index 8fc69d8ad5e99b130a5ee1f9553ee7f8c951d17e..aa27be830992f568a8e7a4f9397f430229713b71 100644 (file)
@@ -85,6 +85,10 @@ th.nowrap {
   margin-top: 16px;
 }
 
+.huge-spacer-bottom {
+  margin-bottom: 40px;
+}
+
 .huge-spacer-top {
   margin-top: 40px;
 }
index 9847d87119a30c938bc60448630b7ec79e6b280b..b5ebfb7b796e7d9a632576959fc2f2104c349bac 100644 (file)
@@ -38,6 +38,7 @@ module.exports = {
   gray71: '#b4b4b4',
   gray67: '#aaa',
   gray60: '#999',
+  gray40: '#404040',
 
   barBackgroundColor: '#f3f3f3',
   barBorderColor: '#e6e6e6',
@@ -48,6 +49,8 @@ module.exports = {
   leakColor: '#fbf3d5',
   leakBorderColor: '#eae3c7',
 
+  snippetFontColor: '#f0f0f0',
+
   // sizes
   gridSize: `${grid}px`,
 
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx
new file mode 100644 (file)
index 0000000..a43aa00
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 * as classNames from 'classnames';
+import { BadgeType } from './utils';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  onClick: (type: BadgeType) => void;
+  selected: boolean;
+  type: BadgeType;
+  url: string;
+}
+
+export default class BadgeButton extends React.PureComponent<Props> {
+  handleClick = () => this.props.onClick(this.props.type);
+
+  render() {
+    const { selected, type, url } = this.props;
+    const width = type !== BadgeType.measure ? '128px' : undefined;
+    return (
+      <button className={classNames('badge-button', { selected })} onClick={this.handleClick}>
+        <img src={url} alt={translate('overview.badges', type, 'alt')} width={width} />
+      </button>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx
new file mode 100644 (file)
index 0000000..e0b1ca1
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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 { connect } from 'react-redux';
+import Select from '../../../components/controls/Select';
+import { fetchWebApi } from '../../../api/web-api';
+import { Metric } from '../../../app/types';
+import { BadgeColors, BadgeType, BadgeOptions } from './utils';
+import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
+import { fetchMetrics } from '../../../store/rootActions';
+import { getMetrics } from '../../../store/rootReducer';
+
+interface StateToProps {
+  metrics: { [key: string]: Metric };
+}
+
+interface DispatchToProps {
+  fetchMetrics: () => void;
+}
+
+interface OwnProps {
+  className?: string;
+  options: BadgeOptions;
+  type: BadgeType;
+  updateOptions: (options: Partial<BadgeOptions>) => void;
+}
+
+type Props = StateToProps & DispatchToProps & OwnProps;
+
+interface State {
+  badgeMetrics: string[];
+}
+
+export class BadgeParams extends React.PureComponent<Props> {
+  mounted: boolean;
+  state: State = { badgeMetrics: [] };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.props.fetchMetrics();
+    this.fetchBadgeMetrics();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchBadgeMetrics() {
+    fetchWebApi(false).then(
+      webservices => {
+        if (this.mounted) {
+          const domain = webservices.find(domain => domain.path === 'api/project_badges');
+          const ws = domain && domain.actions.find(ws => ws.key === 'measure');
+          const param = ws && ws.params && ws.params.find(param => param.key === 'metric');
+          if (param && param.possibleValues) {
+            this.setState({ badgeMetrics: param.possibleValues });
+          }
+        }
+      },
+      () => {}
+    );
+  }
+
+  getColorOptions = () =>
+    ['white', 'black', 'orange'].map(color => ({
+      label: translate('overview.badges.options.colors', color),
+      value: color
+    }));
+
+  getMetricOptions = () => {
+    const { metrics } = this.props;
+    return this.state.badgeMetrics.map(key => {
+      const metric = metrics[key];
+      return {
+        value: key,
+        label: metric && getLocalizedMetricName(metric)
+      };
+    });
+  };
+
+  handleColorChange = ({ value }: { value: BadgeColors }) =>
+    this.props.updateOptions({ color: value });
+
+  handleMetricChange = ({ value }: { value: string }) =>
+    this.props.updateOptions({ metric: value });
+
+  render() {
+    const { className, options, type } = this.props;
+    switch (type) {
+      case BadgeType.marketing:
+        return (
+          <div className={className}>
+            <label className="big-spacer-right" htmlFor="badge-color">
+              {translate('color')}
+            </label>
+            <Select
+              className="input-medium"
+              clearable={false}
+              name="badge-color"
+              onChange={this.handleColorChange}
+              options={this.getColorOptions()}
+              searchable={false}
+              value={options.color}
+            />
+          </div>
+        );
+      case BadgeType.measure:
+        return (
+          <div className={className}>
+            <label className="big-spacer-right" htmlFor="badge-metric">
+              {translate('overview.badges.metric')}
+            </label>
+            <Select
+              className="input-medium"
+              clearable={false}
+              name="badge-metric"
+              onChange={this.handleMetricChange}
+              options={this.getMetricOptions()}
+              searchable={false}
+              value={options.metric}
+            />
+          </div>
+        );
+      default:
+        return null;
+    }
+  }
+}
+
+const mapDispatchToProps: DispatchToProps = { fetchMetrics };
+
+const mapStateToProps = (state: any): StateToProps => ({
+  metrics: getMetrics(state)
+});
+
+export default connect<StateToProps, DispatchToProps, OwnProps>(
+  mapStateToProps,
+  mapDispatchToProps
+)(BadgeParams);
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx
new file mode 100644 (file)
index 0000000..5deaf71
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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 ClipboardButton from '../../../components/controls/ClipboardButton';
+
+interface Props {
+  snippet: string;
+}
+
+export default function BadgeSnippet({ snippet }: Props) {
+  return (
+    <div className="badge-snippet">
+      <pre>{snippet}</pre>
+      <ClipboardButton copyValue={snippet} tooltipPlacement="top" />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
new file mode 100644 (file)
index 0000000..4eea5f4
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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 Modal from '../../../components/controls/Modal';
+import BadgeButton from './BadgeButton';
+import BadgeSnippet from './BadgeSnippet';
+import BadgeParams from './BadgeParams';
+import { getBadgeUrl, BadgeType, BadgeOptions } from './utils';
+import { translate } from '../../../helpers/l10n';
+import './styles.css';
+
+interface Props {
+  branch: string;
+  project: string;
+}
+
+interface State {
+  open: boolean;
+  selectedType: BadgeType;
+  badgeOptions: BadgeOptions;
+}
+
+export default class BadgesModal extends React.PureComponent<Props, State> {
+  state: State = {
+    open: false,
+    selectedType: BadgeType.measure,
+    badgeOptions: { color: 'white', metric: 'alert_status' }
+  };
+
+  handleClose = () => this.setState({ open: false });
+
+  handleOpen = () => this.setState({ open: true });
+
+  handleSelectBadge = (selectedType: BadgeType) => this.setState({ selectedType });
+
+  handleUpdateOptions = (options: Partial<BadgeOptions>) =>
+    this.setState(state => ({
+      badgeOptions: { ...state.badgeOptions, ...options }
+    }));
+
+  handleCancelClick = () => this.handleClose();
+
+  render() {
+    const { branch, project } = this.props;
+    const { selectedType, badgeOptions } = this.state;
+    const header = translate('overview.badges.title');
+    const fullBadgeOptions = { branch, project, ...badgeOptions };
+    return (
+      <>
+        <button onClick={this.handleOpen}>{translate('overview.badges.get_badge')}</button>
+        {this.state.open && (
+          <Modal contentLabel={header} onRequestClose={this.handleClose}>
+            <header className="modal-head">
+              <h2>{header}</h2>
+            </header>
+            <div className="modal-body">
+              <p className="huge-spacer-bottom">{translate('overview.badges.description')}</p>
+              <div className="badges-list spacer-bottom">
+                {[BadgeType.measure, BadgeType.qualityGate, BadgeType.marketing].map(type => (
+                  <BadgeButton
+                    key={type}
+                    onClick={this.handleSelectBadge}
+                    selected={type === selectedType}
+                    type={type}
+                    url={getBadgeUrl(type, fullBadgeOptions)}
+                  />
+                ))}
+              </div>
+              <p className="text-center note huge-spacer-bottom">
+                {translate('overview.badges', selectedType, 'description')}
+              </p>
+              <BadgeParams
+                className="big-spacer-bottom"
+                options={badgeOptions}
+                type={selectedType}
+                updateOptions={this.handleUpdateOptions}
+              />
+              <BadgeSnippet snippet={getBadgeUrl(selectedType, fullBadgeOptions)} />
+            </div>
+            <footer className="modal-foot">
+              <button className="button-link js-modal-close" onClick={this.handleCancelClick}>
+                {translate('close')}
+              </button>
+            </footer>
+          </Modal>
+        )}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx
new file mode 100644 (file)
index 0000000..3f95869
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 BadgeButton from '../BadgeButton';
+import { BadgeType } from '../utils';
+import { click } from '../../../../helpers/testUtils';
+
+it('should display correctly', () => {
+  expect(getWrapper()).toMatchSnapshot();
+  expect(getWrapper({ selected: true })).toMatchSnapshot();
+  expect(getWrapper({ type: BadgeType.measure })).toMatchSnapshot();
+});
+
+it('should return the badge type on click', () => {
+  const onClick = jest.fn();
+  const wrapper = getWrapper({ onClick });
+  click(wrapper.find('button'));
+  expect(onClick).toHaveBeenCalledWith(BadgeType.marketing);
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <BadgeButton
+      onClick={jest.fn()}
+      selected={false}
+      type={BadgeType.marketing}
+      url="http://foo.bar"
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx
new file mode 100644 (file)
index 0000000..5108e57
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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 { BadgeParams } from '../BadgeParams';
+import { BadgeType } from '../utils';
+import { Metric } from '../../../../app/types';
+
+jest.mock('../../../../api/web-api', () => ({
+  fetchWebApi: () =>
+    Promise.resolve([
+      {
+        path: 'api/project_badges',
+        actions: [
+          {
+            key: 'measure',
+            params: [{ key: 'metric', possibleValues: ['alert_status', 'coverage'] }]
+          }
+        ]
+      }
+    ])
+}));
+
+const METRICS = {
+  alert_status: { key: 'alert_status', name: 'Quality Gate' } as Metric,
+  coverage: { key: 'coverage', name: 'Coverage' } as Metric
+};
+
+it('should display marketing badge params', () => {
+  const updateOptions = jest.fn();
+  const wrapper = getWrapper({ updateOptions });
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.instance() as BadgeParams).handleColorChange({ value: 'black' });
+  expect(updateOptions).toHaveBeenCalledWith({ color: 'black' });
+});
+
+it('should display measure badge params', () => {
+  const updateOptions = jest.fn();
+  const wrapper = getWrapper({ updateOptions, type: BadgeType.measure });
+  expect(wrapper).toMatchSnapshot();
+  (wrapper.instance() as BadgeParams).handleColorChange({ value: 'black' });
+  expect(updateOptions).toHaveBeenCalledWith({ color: 'black' });
+});
+
+function getWrapper(props = {}) {
+  return shallow(
+    <BadgeParams
+      fetchMetrics={jest.fn()}
+      metrics={METRICS}
+      options={{ color: 'white', metric: 'alert_status' }}
+      type={BadgeType.marketing}
+      updateOptions={jest.fn()}
+      {...props}
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
new file mode 100644 (file)
index 0000000..3eee7bf
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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 BadgesModal from '../BadgesModal';
+import { click } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../helpers/urls', () => ({
+  getHostUrl: () => 'host'
+}));
+
+it('should display the modal after click', () => {
+  const wrapper = shallow(<BadgesModal branch="branch-6.6" project="foo" />);
+  expect(wrapper).toMatchSnapshot();
+  click(wrapper.find('button'));
+  expect(wrapper.find('Modal')).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap
new file mode 100644 (file)
index 0000000..90b4d8e
--- /dev/null
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly 1`] = `
+<button
+  className="badge-button"
+  onClick={[Function]}
+>
+  <img
+    alt="overview.badges.marketing.alt"
+    src="http://foo.bar"
+    width="128px"
+  />
+</button>
+`;
+
+exports[`should display correctly 2`] = `
+<button
+  className="badge-button selected"
+  onClick={[Function]}
+>
+  <img
+    alt="overview.badges.marketing.alt"
+    src="http://foo.bar"
+    width="128px"
+  />
+</button>
+`;
+
+exports[`should display correctly 3`] = `
+<button
+  className="badge-button"
+  onClick={[Function]}
+>
+  <img
+    alt="overview.badges.measure.alt"
+    src="http://foo.bar"
+  />
+</button>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap
new file mode 100644 (file)
index 0000000..58bb0b5
--- /dev/null
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display marketing badge params 1`] = `
+<div>
+  <label
+    className="big-spacer-right"
+    htmlFor="badge-color"
+  >
+    color
+  </label>
+  <Select
+    className="input-medium"
+    clearable={false}
+    name="badge-color"
+    onChange={[Function]}
+    options={
+      Array [
+        Object {
+          "label": "overview.badges.options.colors.white",
+          "value": "white",
+        },
+        Object {
+          "label": "overview.badges.options.colors.black",
+          "value": "black",
+        },
+        Object {
+          "label": "overview.badges.options.colors.orange",
+          "value": "orange",
+        },
+      ]
+    }
+    searchable={false}
+    value="white"
+  />
+</div>
+`;
+
+exports[`should display measure badge params 1`] = `
+<div>
+  <label
+    className="big-spacer-right"
+    htmlFor="badge-metric"
+  >
+    overview.badges.metric
+  </label>
+  <Select
+    className="input-medium"
+    clearable={false}
+    name="badge-metric"
+    onChange={[Function]}
+    options={Array []}
+    searchable={false}
+    value="alert_status"
+  />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
new file mode 100644 (file)
index 0000000..c6fd052
--- /dev/null
@@ -0,0 +1,89 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display the modal after click 1`] = `
+<React.Fragment>
+  <button
+    onClick={[Function]}
+  >
+    overview.badges.get_badge
+  </button>
+</React.Fragment>
+`;
+
+exports[`should display the modal after click 2`] = `
+<Modal
+  contentLabel="overview.badges.title"
+  onRequestClose={[Function]}
+>
+  <header
+    className="modal-head"
+  >
+    <h2>
+      overview.badges.title
+    </h2>
+  </header>
+  <div
+    className="modal-body"
+  >
+    <p
+      className="huge-spacer-bottom"
+    >
+      overview.badges.description
+    </p>
+    <div
+      className="badges-list spacer-bottom"
+    >
+      <BadgeButton
+        key="measure"
+        onClick={[Function]}
+        selected={true}
+        type="measure"
+        url="host/api/project_badges/measure?branch=branch-6.6&project=foo&metric=alert_status"
+      />
+      <BadgeButton
+        key="quality_gate"
+        onClick={[Function]}
+        selected={false}
+        type="quality_gate"
+        url="host/api/project_badges/quality_gate?branch=branch-6.6&project=foo"
+      />
+      <BadgeButton
+        key="marketing"
+        onClick={[Function]}
+        selected={false}
+        type="marketing"
+        url="host/images/project_badges/sonarcloud-white.svg"
+      />
+    </div>
+    <p
+      className="text-center note huge-spacer-bottom"
+    >
+      overview.badges.measure.description
+    </p>
+    <Connect(BadgeParams)
+      className="big-spacer-bottom"
+      options={
+        Object {
+          "color": "white",
+          "metric": "alert_status",
+        }
+      }
+      type="measure"
+      updateOptions={[Function]}
+    />
+    <BadgeSnippet
+      snippet="host/api/project_badges/measure?branch=branch-6.6&project=foo&metric=alert_status"
+    />
+  </div>
+  <footer
+    className="modal-foot"
+  >
+    <button
+      className="button-link js-modal-close"
+      onClick={[Function]}
+    >
+      close
+    </button>
+  </footer>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts
new file mode 100644 (file)
index 0000000..59d6438
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 { getBadgeUrl, BadgeOptions, BadgeType } from '../utils';
+
+jest.mock('../../../../helpers/urls', () => ({
+  getHostUrl: () => 'host'
+}));
+
+const options: BadgeOptions = {
+  branch: 'master',
+  color: 'white',
+  project: 'foo',
+  metric: 'alert_status'
+};
+
+describe('#getBadgeUrl', () => {
+  it('should generate correct marketing badge links', () => {
+    expect(getBadgeUrl(BadgeType.marketing, options)).toBe(
+      'host/images/project_badges/sonarcloud-white.svg'
+    );
+    expect(getBadgeUrl(BadgeType.marketing, { ...options, color: 'orange' })).toBe(
+      'host/images/project_badges/sonarcloud-orange.svg'
+    );
+  });
+
+  it('should generate correct quality gate badge links', () => {
+    expect(getBadgeUrl(BadgeType.qualityGate, options));
+  });
+
+  it('should generate correct measures badge links', () => {
+    expect(getBadgeUrl(BadgeType.measure, options)).toBe(
+      'host/api/project_badges/measure?branch=master&project=foo&metric=alert_status'
+    );
+  });
+
+  it('should ignore undefined parameters', () => {
+    expect(getBadgeUrl(BadgeType.measure, { color: 'white', metric: 'alert_status' })).toBe(
+      'host/api/project_badges/measure?metric=alert_status'
+    );
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/styles.css b/server/sonar-web/src/main/js/apps/overview/badges/styles.css
new file mode 100644 (file)
index 0000000..3fe0529
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+.badges-list {
+  display: flex;
+  justify-content: space-around;
+  justify-content: space-evenly;
+  flex-wrap: nowrap;
+}
+
+.badge-button {
+  display: flex;
+  justify-content: center;
+  padding: var(--gridSize);
+  min-width: 146px;
+  height: 116px;
+  background-color: var(--barBackgroundColor);
+  border: solid 1px var(--barBorderColor);
+  border-radius: 3px;
+  transition: all 0.3s ease;
+}
+
+.badge-button:hover,
+.badge-button:focus,
+.badge-button:active {
+  background-color: var(--barBackgroundColor);
+  border-color: var(--blue);
+  box-shadow: none;
+}
+
+.badge-button.selected {
+  background-color: var(--lightBlue);
+  border-color: var(--darkBlue);
+}
+
+.badge-snippet {
+  position: relative;
+  margin: var(--gridSize) 0;
+  background: var(--gray40);
+  color: var(--snippetFontColor);
+  border-radius: 3px;
+}
+
+.badge-snippet pre {
+  padding: calc(2 * var(--gridSize));
+  padding-bottom: 40px;
+  overflow: auto;
+}
+
+.badge-snippet button {
+  position: absolute;
+  top: auto;
+  top: 40px;
+  right: calc(2 * var(--gridSize));
+  height: var(--smallControlHeight);
+  line-height: 18px;
+  border: 1px solid #fff;
+  color: #fff;
+  font-size: 11px;
+  font-weight: normal;
+  user-select: none;
+}
+
+.badge-snippet button:hover,
+.badge-snippet button:focus,
+.badge-snippet button:active {
+  background-color: #fff;
+  color: var(--gray40);
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts
new file mode 100644 (file)
index 0000000..39113f8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 { stringify } from 'querystring';
+import { omitNil } from '../../../helpers/request';
+import { getHostUrl } from '../../../helpers/urls';
+
+export type BadgeColors = 'white' | 'black' | 'orange';
+
+export interface BadgeOptions {
+  branch?: string;
+  color?: BadgeColors;
+  project?: string;
+  metric?: string;
+}
+
+export enum BadgeType {
+  measure = 'measure',
+  qualityGate = 'quality_gate',
+  marketing = 'marketing'
+}
+
+export function getBadgeUrl(
+  type: BadgeType,
+  { branch, project, color = 'white', metric = 'alert_status' }: BadgeOptions
+) {
+  switch (type) {
+    case BadgeType.marketing:
+      return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`;
+    case BadgeType.qualityGate:
+      return `${getHostUrl()}/api/project_badges/quality_gate?${stringify(
+        omitNil({ branch, project })
+      )}`;
+    case BadgeType.measure:
+    default:
+      return `${getHostUrl()}/api/project_badges/measure?${stringify(
+        omitNil({ branch, project, metric })
+      )}`;
+  }
+}
index 9b0f6afe1496109a8698c1c8955e1f83c5ea971a..0422440aa9907463b92502bfa9719cf6f0279166 100644 (file)
@@ -27,7 +27,9 @@ import MetaQualityProfiles from './MetaQualityProfiles';
 import AnalysesList from '../events/AnalysesList';
 import MetaSize from './MetaSize';
 import MetaTags from './MetaTags';
-import { areThereCustomOrganizations } from '../../../store/rootReducer';
+import BadgesModal from '../badges/BadgesModal';
+import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
+import { Visibility } from '../../../app/types';
 
 const Meta = ({
   branch,
@@ -38,9 +40,10 @@ const Meta = ({
   onComponentChange,
   onSonarCloud
 }) => {
-  const { qualifier, description, qualityProfiles, qualityGate } = component;
+  const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
 
   const isProject = qualifier === 'TRK';
+  const isPrivate = visibility === Visibility.Private;
 
   const hasDescription = !!description;
   const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
@@ -87,6 +90,10 @@ const Meta = ({
       <MetaKey component={component} />
 
       {hasOrganization && <MetaOrganizationKey component={component} />}
+
+      {onSonarCloud &&
+        isProject &&
+        !isPrivate && <BadgesModal branch={branch} project={component.key} />}
     </div>
   );
 };
index d5e4ba2fcb9b7ec00bd0501ea2707b0eee12e2cf..e12fb2dde8687749357593821b516edcd6744774 100644 (file)
@@ -66,8 +66,8 @@
 .onboarding-command pre {
   padding: 15px;
   border-radius: 2px;
-  background: #404040;
-  color: #f0f0f0;
+  background: var(--gray40);
+  color: var(--snippetFontColor);
   overflow: auto;
 }
 
@@ -88,7 +88,7 @@
 .onboarding-command button:focus,
 .onboarding-command button:active {
   background-color: #fff;
-  color: #404040;
+  color: var(--gray40);
 }
 
 .onboarding-command-oneline pre {
index 916cabd375f928dab9b69bda0eec89904ae87ef4..49f8cd2e379f9f99193dc31e7ffcc285be3eef93 100644 (file)
@@ -55,8 +55,9 @@ export default class ResponseExample extends React.PureComponent<Props, State> {
 
   fetchResponseExample() {
     const { domain, action } = this.props;
-    fetchResponseExampleApi(domain.path, action.key).then(responseExample =>
-      this.setState({ responseExample })
+    fetchResponseExampleApi(domain.path, action.key).then(
+      responseExample => this.setState({ responseExample }),
+      () => {}
     );
   }
 
index c198d4dd3416564c4abcd39dc41faf1786f0395c..270c821ce00d625d579c643410e1c0468294164c 100644 (file)
@@ -81,12 +81,15 @@ export default class WebApiApp extends React.PureComponent<Props, State> {
     }
   }
 
-  fetchList(cb?: () => void) {
-    fetchWebApi().then(domains => {
-      if (this.mounted) {
-        this.setState({ domains }, cb);
-      }
-    });
+  fetchList() {
+    fetchWebApi().then(
+      domains => {
+        if (this.mounted) {
+          this.setState({ domains });
+        }
+      },
+      () => {}
+    );
   }
 
   scrollToAction = () => {
index 14988f866eb9d5ab15811fecbc8bdcd3dbbd3fe7..89aa7d6e551418de503bba819de23c27f7851c45 100644 (file)
@@ -58,7 +58,7 @@
   margin-top: -1px;
   padding: 5px 10px;
   border-top: 1px solid #e0e0e0;
-  color: #404040;
+  color: var(--gray40);
   transition: transform 0.3s ease;
 }
 
index dc9b146d13e3cc5031528c136b41e6b03feb839d..021c390dd1e3e2903fc89800e96cfef03829f6d6 100644 (file)
@@ -59,7 +59,9 @@ export default class ClipboardButton extends React.PureComponent<Props, State> {
 
   componentWillUnmount() {
     this.mounted = false;
-    this.clipboard.destroy();
+    if (this.clipboard) {
+      this.clipboard.destroy();
+    }
   }
 
   showTooltip = () => {
index 10ab9a82d7aba5f3e5c4b4dd437518df791b5b5f..170a4e2c986dc4809e79a045422686721598d063 100644 (file)
@@ -34,7 +34,7 @@
   vertical-align: middle;
   margin-right: 10px;
   padding: 3px 10px;
-  background-color: #404040;
+  background-color: var(--gray40);
   color: #fff;
   font-size: var(--smallFontSize);
   font-weight: 300;
@@ -63,7 +63,7 @@
   height: 30px;
   padding: 3px 10px;
   box-sizing: border-box;
-  background-color: #404040;
+  background-color: var(--gray40);
   color: #fff;
   font-weight: 300;
 }
diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg
new file mode 100644 (file)
index 0000000..81d55ae
--- /dev/null
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 535 122" viewBox="0 0 535 122" width="128" height="30">
+<path d="m523.1 120h-510.7c-5.7 0-10.4-4.7-10.4-10.4v-97.2c0-5.7 4.7-10.4 10.4-10.4h510.7c5.7 0 10.4 4.7 10.4 10.4v97.2c0 5.7-4.7 10.4-10.4 10.4z" fill="#070706"/>
+<g fill="#fff">
+  <path d="m135 89.6c2.4 1.5 7.5 3.2 11.4 3.2 4 0 5.6-1.4 5.6-3.6s-1.3-3.2-6.2-4.9c-8.8-2.9-12.1-7.7-12-12.7 0-7.9 6.8-13.9 17.2-13.9 4.9 0 9.4 1.1 12 2.4l-2.3 9.1c-1.9-1-5.5-2.4-9.2-2.4-3.2 0-5 1.3-5 3.5 0 2 1.6 3 6.8 4.9 8.1 2.8 11.4 6.8 11.5 13.1 0 7.9-6.2 13.7-18.4 13.7-5.5 0-10.5-1.2-13.7-2.9z"/>
+  <path d="m211.1 79.4c0 15.5-11 22.6-22.4 22.6-12.4 0-21.9-8.1-21.9-21.8s9-22.4 22.6-22.4c13.1 0 21.7 8.9 21.7 21.6zm-30.6.5c0 7.3 3 12.7 8.7 12.7 5.1 0 8.4-5.1 8.4-12.7 0-6.3-2.4-12.7-8.4-12.7-6.4-.1-8.7 6.4-8.7 12.7z"/>
+  <path d="m215.8 72.2c0-5.3-.2-9.8-.3-13.5h11.4l.6 5.8h.3c1.7-2.7 6.1-6.8 13.1-6.8 8.7 0 15.2 5.7 15.2 18.2v25.1h-13.2v-23.4c0-5.5-1.9-9.2-6.7-9.2-3.6 0-5.8 2.5-6.7 4.9-.3.8-.5 2.1-.5 3.3v24.4h-13.2z"/>
+  <path d="m287.2 101.1-.8-4.2h-.3c-2.8 3.4-7.1 5.2-12.1 5.2-8.6 0-13.7-6.2-13.7-13 0-11 9.9-16.3 24.9-16.2v-.6c0-2.3-1.2-5.5-7.7-5.5-4.3 0-8.9 1.5-11.7 3.2l-2.4-8.5c2.9-1.6 8.8-3.7 16.5-3.7 14.1 0 18.6 8.3 18.6 18.3v14.7c0 4.1.2 8 .6 10.3zm-1.5-20c-6.9-.1-12.3 1.6-12.3 6.7 0 3.4 2.3 5 5.2 5 3.3 0 6-2.2 6.8-4.9.2-.7.3-1.5.3-2.3z"/>
+  <path d="m305.4 72.7c0-6.2-.2-10.3-.3-14h11.4l.4 7.8h.3c2.2-6.2 7.4-8.8 11.4-8.8 1.2 0 1.8 0 2.8.2v12.4c-1-.2-2.1-.3-3.6-.3-4.9 0-8.1 2.6-9 6.7-.2.9-.3 1.9-.3 2.9v21.5h-13.2v-28.4z"/>
+  <path d="m367.6 99.5c-2 1-6.4 2.4-12 2.4-12.7 0-20.9-8.6-20.9-21.4 0-12.9 8.8-22.3 22.5-22.3 4.5 0 8.5 1.1 10.6 2.2l-1.7 5.9c-1.8-1-4.7-2-8.8-2-9.6 0-14.8 7.1-14.8 15.9 0 9.7 6.2 15.7 14.6 15.7 4.3 0 7.2-1.1 9.4-2.1z"/>
+  <path d="m373 39.6h7.6v61.5h-7.6z"/>
+  <path d="m428 79.8c0 15.5-10.7 22.3-20.9 22.3-11.4 0-20.1-8.3-20.1-21.6 0-14 9.2-22.3 20.8-22.3 12 0 20.2 8.7 20.2 21.6zm-33.3.4c0 9.2 5.3 16.1 12.7 16.1 7.3 0 12.7-6.8 12.7-16.3 0-7.1-3.6-16.1-12.6-16.1-8.9 0-12.8 8.3-12.8 16.3z"/>
+  <path d="m469.2 89.7c0 4.3.1 8.1.3 11.4h-6.8l-.4-6.8h-.2c-2 3.4-6.4 7.8-13.9 7.8-6.6 0-14.5-3.6-14.5-18.4v-24.6h7.6v23.2c0 8 2.4 13.3 9.4 13.3 5.1 0 8.7-3.6 10.1-6.9.4-1.1.7-2.5.7-3.9v-25.7h7.6v30.6z"/>
+  <path d="m514.6 39.6v50.7c0 3.7.1 8 .3 10.8h-6.8l-.3-7.3h-.2c-2.3 4.7-7.5 8.2-14.3 8.2-10.1 0-17.9-8.6-17.9-21.3-.1-14 8.6-22.5 18.8-22.5 6.4 0 10.7 3 12.7 6.4h.2v-25zm-7.6 36.6c0-1-.1-2.3-.3-3.2-1.1-4.9-5.3-8.8-11-8.8-7.9 0-12.6 6.9-12.6 16.2 0 8.5 4.2 15.5 12.4 15.5 5.1 0 9.8-3.4 11.2-9.1.3-1 .3-2.1.3-3.3z"/>
+</g>
+<path d="m111.3 56.9c-3.6-4.4-8.5-7.7-14-9.4 0-.1 0-.2 0-.3 0-16.1-13-29.2-29.1-29.2s-29.2 13.1-29.2 29.2v.4c-11.8 3.7-20.4 14.8-20.4 27.8 0 16.1 13.1 29.2 29.2 29.2 7.8 0 15.1-3 20.5-8.4 5.3 5.2 12.5 8.4 20.5 8.4 16.1 0 29.2-13.1 29.2-29.2-.1-6.7-2.4-13.3-6.7-18.5zm-22.6 40.3c-12 0-21.8-9.8-21.8-21.8 0-2-1.6-3.7-3.7-3.7-2 0-3.7 1.6-3.7 3.7 0 5.5 1.5 10.6 4.1 14.9-4.1 4.4-9.8 6.9-15.9 6.9-12 0-21.8-9.8-21.8-21.8s9.8-21.8 21.8-21.8c2.6 0 5.1.5 7.5 1.3h.1c.8.3 2 .8 2.4 1.1 1.5 1.3 3.9 1.1 5.2-.4s1.1-3.9-.4-5.2c-1.6-1.4-4.1-2.3-4.7-2.5-3.2-1.2-6.6-1.8-10-1.8-.5 0-.9 0-1.4 0 .5-11.6 10.1-20.9 21.8-20.9 12 0 21.8 9.8 21.8 21.8 0 7-3.4 13.7-9.2 17.8-1.7 1.2-2 3.5-.9 5.1.7 1 1.8 1.5 3 1.5.7 0 1.5-.2 2.1-.7 5.5-3.9 9.4-9.6 11.2-16 8.4 3.1 14.3 11.3 14.3 20.5.1 12.2-9.7 22-21.8 22z" fill="#f60"/>
+<path d="m133.4 40.8c1.3.8 3.9 1.7 6 1.7 2.6 0 3.7-1.1 3.7-2.6 0-1.6-1-2.4-3.8-3.4-4.5-1.6-6.5-4.1-6.4-6.8 0-4.1 3.4-7.3 8.8-7.3 2.6 0 4.8.7 6.2 1.4l-1.1 4.2c-1-.6-2.9-1.3-4.9-1.3-2.1 0-3.3 1-3.3 2.4 0 1.5 1.1 2.2 4.1 3.3 4.2 1.5 6.2 3.7 6.2 7.1 0 4.2-3.3 7.3-9.5 7.3-2.8 0-5.4-.7-7.1-1.6z" fill="#fff"/>
+<path d="m170.7 45.5c-1.2.6-3.7 1.2-6.6 1.2-7.3 0-12-4.6-12-11.9 0-7 4.8-12.4 13-12.4 2.2 0 4.3.5 5.7 1.1l-1.1 4.4c-1-.4-2.3-.9-4.4-.9-4.5 0-7.2 3.3-7.1 7.6 0 4.8 3.1 7.5 7.1 7.5 2.1 0 3.5-.4 4.6-.9z" fill="#fff"/>
+<path d="m188.2 46.2-.4-2.6h-.1c-1.4 1.8-3.9 3.1-6.9 3.1-4.7 0-7.3-3.4-7.3-6.9 0-5.9 5.2-8.8 13.8-8.8v-.4c0-1.5-.6-4.1-4.7-4.1-2.3 0-4.7.7-6.3 1.7l-1.1-3.8c1.7-1.1 4.7-2.1 8.4-2.1 7.5 0 9.6 4.7 9.6 9.8v8.4c0 2.1.1 4.2.3 5.6h-5.3zm-.8-11.4c-4.2-.1-8.1.8-8.1 4.4 0 2.3 1.5 3.3 3.3 3.3 2.3 0 4.1-1.5 4.6-3.2.1-.4.2-.9.2-1.3z" fill="#fff"/>
+<path d="m199.4 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z" fill="#fff"/>
+<path d="m226.7 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z" fill="#fff"/>
+<path d="m258.2 36.2c.1 4.2 3.4 6 7.2 6 2.7 0 4.7-.4 6.5-1.1l.9 4.1c-2 .8-4.8 1.4-8.1 1.4-7.6 0-12-4.6-12-11.8 0-6.5 3.9-12.5 11.4-12.5 7.6 0 10 6.2 10 11.3 0 1.1-.1 2-.2 2.5h-15.7zm10.2-4.1c0-2.2-.9-5.7-4.8-5.7-3.6 0-5.2 3.3-5.4 5.7z" fill="#fff"/>
+<path d="m300.2 12.2v27.4c0 2.4.1 5 .2 6.5h-5.3l-.2-3.7h-.1c-1.4 2.6-4.2 4.2-7.6 4.2-5.5 0-9.9-4.7-9.9-11.9 0-7.8 4.8-12.4 10.4-12.4 3.2 0 5.5 1.3 6.5 3.1h.1v-13.2zm-5.9 20.4c0-.5 0-1.1-.1-1.5-.5-2.3-2.4-4.2-5.1-4.2-3.8 0-5.9 3.3-5.9 7.7 0 4.3 2.1 7.4 5.8 7.4 2.4 0 4.5-1.6 5.1-4.2.1-.5.2-1.1.2-1.7z" fill="#fff"/>
+<path d="m338.5 34.3c0 8.6-6 12.4-12 12.4-6.6 0-11.7-4.5-11.7-12 0-7.6 5-12.3 12-12.3 7.1 0 11.7 4.8 11.7 11.9zm-17.5.3c0 4.5 2.2 7.9 5.8 7.9 3.3 0 5.7-3.3 5.7-8 0-3.6-1.6-7.8-5.6-7.8-4.3 0-5.9 4-5.9 7.9z" fill="#fff"/>
+<path d="m343.3 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z" fill="#fff"/>
+</svg>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg
new file mode 100644 (file)
index 0000000..20ea73b
--- /dev/null
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 535 122" viewBox="0 0 535 122" width="128" height="30">
+<path d="m523.1 120h-510.7c-5.7 0-10.4-4.7-10.4-10.4v-97.2c0-5.7 4.7-10.4 10.4-10.4h510.7c5.7 0 10.4 4.7 10.4 10.4v97.2c0 5.7-4.7 10.4-10.4 10.4z" fill="#f60"/>
+<g fill="#fff">
+  <path d="m135 89.6c2.4 1.5 7.5 3.2 11.4 3.2 4 0 5.6-1.4 5.6-3.6s-1.3-3.2-6.2-4.9c-8.8-2.9-12.1-7.7-12-12.7 0-7.9 6.8-13.9 17.2-13.9 4.9 0 9.4 1.1 12 2.4l-2.3 9.1c-1.9-1-5.5-2.4-9.2-2.4-3.2 0-5 1.3-5 3.5 0 2 1.6 3 6.8 4.9 8.1 2.8 11.4 6.8 11.5 13.1 0 7.9-6.2 13.7-18.4 13.7-5.5 0-10.5-1.2-13.7-2.9z"/>
+  <path d="m211.1 79.4c0 15.5-11 22.6-22.4 22.6-12.4 0-21.9-8.1-21.9-21.8s9-22.4 22.6-22.4c13.1 0 21.7 8.9 21.7 21.6zm-30.6.5c0 7.3 3 12.7 8.7 12.7 5.1 0 8.4-5.1 8.4-12.7 0-6.3-2.4-12.7-8.4-12.7-6.4-.1-8.7 6.4-8.7 12.7z"/>
+  <path d="m215.8 72.2c0-5.3-.2-9.8-.3-13.5h11.4l.6 5.8h.3c1.7-2.7 6.1-6.8 13.1-6.8 8.7 0 15.2 5.7 15.2 18.2v25.1h-13.2v-23.4c0-5.5-1.9-9.2-6.7-9.2-3.6 0-5.8 2.5-6.7 4.9-.3.8-.5 2.1-.5 3.3v24.4h-13.2z"/>
+  <path d="m287.2 101.1-.8-4.2h-.3c-2.8 3.4-7.1 5.2-12.1 5.2-8.6 0-13.7-6.2-13.7-13 0-11 9.9-16.3 24.9-16.2v-.6c0-2.3-1.2-5.5-7.7-5.5-4.3 0-8.9 1.5-11.7 3.2l-2.4-8.5c2.9-1.6 8.8-3.7 16.5-3.7 14.1 0 18.6 8.3 18.6 18.3v14.7c0 4.1.2 8 .6 10.3zm-1.5-20c-6.9-.1-12.3 1.6-12.3 6.7 0 3.4 2.3 5 5.2 5 3.3 0 6-2.2 6.8-4.9.2-.7.3-1.5.3-2.3z"/>
+  <path d="m305.4 72.7c0-6.2-.2-10.3-.3-14h11.4l.4 7.8h.3c2.2-6.2 7.4-8.8 11.4-8.8 1.2 0 1.8 0 2.8.2v12.4c-1-.2-2.1-.3-3.6-.3-4.9 0-8.1 2.6-9 6.7-.2.9-.3 1.9-.3 2.9v21.5h-13.2v-28.4z"/>
+  <path d="m367.6 99.5c-2 1-6.4 2.4-12 2.4-12.7 0-20.9-8.6-20.9-21.4 0-12.9 8.8-22.3 22.5-22.3 4.5 0 8.5 1.1 10.6 2.2l-1.7 5.9c-1.8-1-4.7-2-8.8-2-9.6 0-14.8 7.1-14.8 15.9 0 9.7 6.2 15.7 14.6 15.7 4.3 0 7.2-1.1 9.4-2.1z"/>
+  <path d="m373 39.6h7.6v61.5h-7.6z"/>
+  <path d="m428 79.8c0 15.5-10.7 22.3-20.9 22.3-11.4 0-20.1-8.3-20.1-21.6 0-14 9.2-22.3 20.8-22.3 12 0 20.2 8.7 20.2 21.6zm-33.3.4c0 9.2 5.3 16.1 12.7 16.1 7.3 0 12.7-6.8 12.7-16.3 0-7.1-3.6-16.1-12.6-16.1-8.9 0-12.8 8.3-12.8 16.3z"/>
+  <path d="m469.2 89.7c0 4.3.1 8.1.3 11.4h-6.8l-.4-6.8h-.2c-2 3.4-6.4 7.8-13.9 7.8-6.6 0-14.5-3.6-14.5-18.4v-24.6h7.6v23.2c0 8 2.4 13.3 9.4 13.3 5.1 0 8.7-3.6 10.1-6.9.4-1.1.7-2.5.7-3.9v-25.7h7.6v30.6z"/>
+  <path d="m514.6 39.6v50.7c0 3.7.1 8 .3 10.8h-6.8l-.3-7.3h-.2c-2.3 4.7-7.5 8.2-14.3 8.2-10.1 0-17.9-8.6-17.9-21.3-.1-14 8.6-22.5 18.8-22.5 6.4 0 10.7 3 12.7 6.4h.2v-25zm-7.6 36.6c0-1-.1-2.3-.3-3.2-1.1-4.9-5.3-8.8-11-8.8-7.9 0-12.6 6.9-12.6 16.2 0 8.5 4.2 15.5 12.4 15.5 5.1 0 9.8-3.4 11.2-9.1.3-1 .3-2.1.3-3.3z"/>
+  <path d="m111.3 56.9c-3.6-4.4-8.5-7.7-14-9.4 0-.1 0-.2 0-.3 0-16.1-13-29.2-29.1-29.2s-29.2 13.1-29.2 29.2v.4c-11.8 3.7-20.4 14.8-20.4 27.8 0 16.1 13.1 29.2 29.2 29.2 7.8 0 15.1-3 20.5-8.4 5.3 5.2 12.5 8.4 20.5 8.4 16.1 0 29.2-13.1 29.2-29.2-.1-6.7-2.4-13.3-6.7-18.5zm-22.6 40.3c-12 0-21.8-9.8-21.8-21.8 0-2-1.6-3.7-3.7-3.7-2 0-3.7 1.6-3.7 3.7 0 5.5 1.5 10.6 4.1 14.9-4.1 4.4-9.8 6.9-15.9 6.9-12 0-21.8-9.8-21.8-21.8s9.8-21.8 21.8-21.8c2.6 0 5.1.5 7.5 1.3h.1c.8.3 2 .8 2.4 1.1 1.5 1.3 3.9 1.1 5.2-.4s1.1-3.9-.4-5.2c-1.6-1.4-4.1-2.3-4.7-2.5-3.2-1.2-6.6-1.8-10-1.8-.5 0-.9 0-1.4 0 .5-11.6 10.1-20.9 21.8-20.9 12 0 21.8 9.8 21.8 21.8 0 7-3.4 13.7-9.2 17.8-1.7 1.2-2 3.5-.9 5.1.7 1 1.8 1.5 3 1.5.7 0 1.5-.2 2.1-.7 5.5-3.9 9.4-9.6 11.2-16 8.4 3.1 14.3 11.3 14.3 20.5.1 12.2-9.7 22-21.8 22z"/>
+  <path d="m133.4 40.8c1.3.8 3.9 1.7 6 1.7 2.6 0 3.7-1.1 3.7-2.6 0-1.6-1-2.4-3.8-3.4-4.5-1.6-6.5-4.1-6.4-6.8 0-4.1 3.4-7.3 8.8-7.3 2.6 0 4.8.7 6.2 1.4l-1.1 4.2c-1-.6-2.9-1.3-4.9-1.3-2.1 0-3.3 1-3.3 2.4 0 1.5 1.1 2.2 4.1 3.3 4.2 1.5 6.2 3.7 6.2 7.1 0 4.2-3.3 7.3-9.5 7.3-2.8 0-5.4-.7-7.1-1.6z"/>
+  <path d="m170.7 45.5c-1.2.6-3.7 1.2-6.6 1.2-7.3 0-12-4.6-12-11.9 0-7 4.8-12.4 13-12.4 2.2 0 4.3.5 5.7 1.1l-1.1 4.4c-1-.4-2.3-.9-4.4-.9-4.5 0-7.2 3.3-7.1 7.6 0 4.8 3.1 7.5 7.1 7.5 2.1 0 3.5-.4 4.6-.9z"/>
+  <path d="m188.2 46.2-.4-2.6h-.1c-1.4 1.8-3.9 3.1-6.9 3.1-4.7 0-7.3-3.4-7.3-6.9 0-5.9 5.2-8.8 13.8-8.8v-.4c0-1.5-.6-4.1-4.7-4.1-2.3 0-4.7.7-6.3 1.7l-1.1-3.8c1.7-1.1 4.7-2.1 8.4-2.1 7.5 0 9.6 4.7 9.6 9.8v8.4c0 2.1.1 4.2.3 5.6h-5.3zm-.8-11.4c-4.2-.1-8.1.8-8.1 4.4 0 2.3 1.5 3.3 3.3 3.3 2.3 0 4.1-1.5 4.6-3.2.1-.4.2-.9.2-1.3z"/>
+  <path d="m199.4 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+  <path d="m226.7 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+  <path d="m258.2 36.2c.1 4.2 3.4 6 7.2 6 2.7 0 4.7-.4 6.5-1.1l.9 4.1c-2 .8-4.8 1.4-8.1 1.4-7.6 0-12-4.6-12-11.8 0-6.5 3.9-12.5 11.4-12.5 7.6 0 10 6.2 10 11.3 0 1.1-.1 2-.2 2.5h-15.7zm10.2-4.1c0-2.2-.9-5.7-4.8-5.7-3.6 0-5.2 3.3-5.4 5.7z"/>
+  <path d="m300.2 12.2v27.4c0 2.4.1 5 .2 6.5h-5.3l-.2-3.7h-.1c-1.4 2.6-4.2 4.2-7.6 4.2-5.5 0-9.9-4.7-9.9-11.9 0-7.8 4.8-12.4 10.4-12.4 3.2 0 5.5 1.3 6.5 3.1h.1v-13.2zm-5.9 20.4c0-.5 0-1.1-.1-1.5-.5-2.3-2.4-4.2-5.1-4.2-3.8 0-5.9 3.3-5.9 7.7 0 4.3 2.1 7.4 5.8 7.4 2.4 0 4.5-1.6 5.1-4.2.1-.5.2-1.1.2-1.7z"/>
+  <path d="m338.5 34.3c0 8.6-6 12.4-12 12.4-6.6 0-11.7-4.5-11.7-12 0-7.6 5-12.3 12-12.3 7.1 0 11.7 4.8 11.7 11.9zm-17.5.3c0 4.5 2.2 7.9 5.8 7.9 3.3 0 5.7-3.3 5.7-8 0-3.6-1.6-7.8-5.6-7.8-4.3 0-5.9 4-5.9 7.9z"/>
+  <path d="m343.3 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+</g>
+</svg>
\ No newline at end of file
diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg
new file mode 100644 (file)
index 0000000..f18a550
--- /dev/null
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 535 122" viewBox="0 0 535 122" width="128" height="30">
+<path d="m522.2 119.5h-508.8c-5.7 0-10.3-4.7-10.3-10.3v-96.4c-.1-5.6 4.6-10.3 10.3-10.3h508.8c5.7 0 10.3 4.7 10.3 10.3v96.3c0 5.7-4.7 10.4-10.3 10.4z" fill="#fff"/>
+<path d="m522.2 121h-508.8c-6.5 0-11.8-5.3-11.8-11.8v-96.4c-.1-6.5 5.2-11.8 11.8-11.8h508.8c6.5 0 11.8 5.3 11.8 11.8v96.3c0 6.6-5.3 11.9-11.8 11.9zm-508.8-117c-4.9 0-8.9 4-8.9 8.9v96.3c0 4.9 4 8.9 8.9 8.9h508.8c4.9 0 8.9-4 8.9-8.9v-96.4c0-4.9-4-8.9-8.9-8.9h-508.8z" fill="#cfd3d7"/>
+<path d="m135 89.6c2.4 1.5 7.5 3.2 11.4 3.2 4 0 5.6-1.4 5.6-3.6s-1.3-3.2-6.2-4.9c-8.8-2.9-12.1-7.7-12-12.7 0-7.9 6.8-13.9 17.2-13.9 4.9 0 9.4 1.1 12 2.4l-2.3 9.1c-1.9-1-5.5-2.4-9.2-2.4-3.2 0-5 1.3-5 3.5 0 2 1.6 3 6.8 4.9 8.1 2.8 11.4 6.8 11.5 13.1 0 7.9-6.2 13.7-18.4 13.7-5.5 0-10.5-1.2-13.7-2.9z"/>
+<path d="m211.1 79.4c0 15.5-11 22.6-22.4 22.6-12.4 0-21.9-8.1-21.9-21.8s9-22.4 22.6-22.4c13.1 0 21.7 8.9 21.7 21.6zm-30.6.5c0 7.3 3 12.7 8.7 12.7 5.1 0 8.4-5.1 8.4-12.7 0-6.3-2.4-12.7-8.4-12.7-6.4-.1-8.7 6.4-8.7 12.7z"/>
+<path d="m215.8 72.2c0-5.3-.2-9.8-.3-13.5h11.4l.6 5.8h.3c1.7-2.7 6.1-6.8 13.1-6.8 8.7 0 15.2 5.7 15.2 18.2v25.1h-13.2v-23.4c0-5.5-1.9-9.2-6.7-9.2-3.6 0-5.8 2.5-6.7 4.9-.3.8-.5 2.1-.5 3.3v24.4h-13.2z"/>
+<path d="m287.2 101.1-.8-4.2h-.3c-2.8 3.4-7.1 5.2-12.1 5.2-8.6 0-13.7-6.2-13.7-13 0-11 9.9-16.3 24.9-16.2v-.6c0-2.3-1.2-5.5-7.7-5.5-4.3 0-8.9 1.5-11.7 3.2l-2.4-8.5c2.9-1.6 8.8-3.7 16.5-3.7 14.1 0 18.6 8.3 18.6 18.3v14.7c0 4.1.2 8 .6 10.3zm-1.5-20c-6.9-.1-12.3 1.6-12.3 6.7 0 3.4 2.3 5 5.2 5 3.3 0 6-2.2 6.8-4.9.2-.7.3-1.5.3-2.3z"/>
+<path d="m305.4 72.7c0-6.2-.2-10.3-.3-14h11.4l.4 7.8h.3c2.2-6.2 7.4-8.8 11.4-8.8 1.2 0 1.8 0 2.8.2v12.4c-1-.2-2.1-.3-3.6-.3-4.9 0-8.1 2.6-9 6.7-.2.9-.3 1.9-.3 2.9v21.5h-13.2v-28.4z"/>
+<path d="m367.6 99.5c-2 1-6.4 2.4-12 2.4-12.7 0-20.9-8.6-20.9-21.4 0-12.9 8.8-22.3 22.5-22.3 4.5 0 8.5 1.1 10.6 2.2l-1.7 5.9c-1.8-1-4.7-2-8.8-2-9.6 0-14.8 7.1-14.8 15.9 0 9.7 6.2 15.7 14.6 15.7 4.3 0 7.2-1.1 9.4-2.1z"/>
+<path d="m373 39.6h7.6v61.5h-7.6z"/>
+<path d="m428 79.8c0 15.5-10.7 22.3-20.9 22.3-11.4 0-20.1-8.3-20.1-21.6 0-14 9.2-22.3 20.8-22.3 12 0 20.2 8.7 20.2 21.6zm-33.3.4c0 9.2 5.3 16.1 12.7 16.1 7.3 0 12.7-6.8 12.7-16.3 0-7.1-3.6-16.1-12.6-16.1-8.9 0-12.8 8.3-12.8 16.3z"/>
+<path d="m469.2 89.7c0 4.3.1 8.1.3 11.4h-6.8l-.4-6.8h-.2c-2 3.4-6.4 7.8-13.9 7.8-6.6 0-14.5-3.6-14.5-18.4v-24.6h7.6v23.2c0 8 2.4 13.3 9.4 13.3 5.1 0 8.7-3.6 10.1-6.9.4-1.1.7-2.5.7-3.9v-25.7h7.6v30.6z"/>
+<path d="m514.6 39.6v50.7c0 3.7.1 8 .3 10.8h-6.8l-.3-7.3h-.2c-2.3 4.7-7.5 8.2-14.3 8.2-10.1 0-17.9-8.6-17.9-21.3-.1-14 8.6-22.5 18.8-22.5 6.4 0 10.7 3 12.7 6.4h.2v-25zm-7.6 36.6c0-1-.1-2.3-.3-3.2-1.1-4.9-5.3-8.8-11-8.8-7.9 0-12.6 6.9-12.6 16.2 0 8.5 4.2 15.5 12.4 15.5 5.1 0 9.8-3.4 11.2-9.1.3-1 .3-2.1.3-3.3z"/>
+<path d="m111.3 56.9c-3.6-4.4-8.5-7.7-14-9.4 0-.1 0-.2 0-.3 0-16.1-13-29.2-29.1-29.2s-29.2 13.1-29.2 29.2v.4c-11.8 3.7-20.4 14.8-20.4 27.8 0 16.1 13.1 29.2 29.2 29.2 7.8 0 15.1-3 20.5-8.4 5.3 5.2 12.5 8.4 20.5 8.4 16.1 0 29.2-13.1 29.2-29.2-.1-6.7-2.4-13.3-6.7-18.5zm-22.6 40.3c-12 0-21.8-9.8-21.8-21.8 0-2-1.6-3.7-3.7-3.7-2 0-3.7 1.6-3.7 3.7 0 5.5 1.5 10.6 4.1 14.9-4.1 4.4-9.8 6.9-15.9 6.9-12 0-21.8-9.8-21.8-21.8s9.8-21.8 21.8-21.8c2.6 0 5.1.5 7.5 1.3h.1c.8.3 2 .8 2.4 1.1 1.5 1.3 3.9 1.1 5.2-.4s1.1-3.9-.4-5.2c-1.6-1.4-4.1-2.3-4.7-2.5-3.2-1.2-6.6-1.8-10-1.8-.5 0-.9 0-1.4 0 .5-11.6 10.1-20.9 21.8-20.9 12 0 21.8 9.8 21.8 21.8 0 7-3.4 13.7-9.2 17.8-1.7 1.2-2 3.5-.9 5.1.7 1 1.8 1.5 3 1.5.7 0 1.5-.2 2.1-.7 5.5-3.9 9.4-9.6 11.2-16 8.4 3.1 14.3 11.3 14.3 20.5.1 12.2-9.7 22-21.8 22z" fill="#f60"/>
+<path d="m133.4 40.8c1.3.8 3.9 1.7 6 1.7 2.6 0 3.7-1.1 3.7-2.6 0-1.6-1-2.4-3.8-3.4-4.5-1.6-6.5-4.1-6.4-6.8 0-4.1 3.4-7.3 8.8-7.3 2.6 0 4.8.7 6.2 1.4l-1.1 4.2c-1-.6-2.9-1.3-4.9-1.3-2.1 0-3.3 1-3.3 2.4 0 1.5 1.1 2.2 4.1 3.3 4.2 1.5 6.2 3.7 6.2 7.1 0 4.2-3.3 7.3-9.5 7.3-2.8 0-5.4-.7-7.1-1.6z"/>
+<path d="m170.7 45.5c-1.2.6-3.7 1.2-6.6 1.2-7.3 0-12-4.6-12-11.9 0-7 4.8-12.4 13-12.4 2.2 0 4.3.5 5.7 1.1l-1.1 4.4c-1-.4-2.3-.9-4.4-.9-4.5 0-7.2 3.3-7.1 7.6 0 4.8 3.1 7.5 7.1 7.5 2.1 0 3.5-.4 4.6-.9z"/>
+<path d="m188.2 46.2-.4-2.6h-.1c-1.4 1.8-3.9 3.1-6.9 3.1-4.7 0-7.3-3.4-7.3-6.9 0-5.9 5.2-8.8 13.8-8.8v-.4c0-1.5-.6-4.1-4.7-4.1-2.3 0-4.7.7-6.3 1.7l-1.1-3.8c1.7-1.1 4.7-2.1 8.4-2.1 7.5 0 9.6 4.7 9.6 9.8v8.4c0 2.1.1 4.2.3 5.6h-5.3zm-.8-11.4c-4.2-.1-8.1.8-8.1 4.4 0 2.3 1.5 3.3 3.3 3.3 2.3 0 4.1-1.5 4.6-3.2.1-.4.2-.9.2-1.3z"/>
+<path d="m199.4 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+<path d="m226.7 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+<path d="m258.2 36.2c.1 4.2 3.4 6 7.2 6 2.7 0 4.7-.4 6.5-1.1l.9 4.1c-2 .8-4.8 1.4-8.1 1.4-7.6 0-12-4.6-12-11.8 0-6.5 3.9-12.5 11.4-12.5 7.6 0 10 6.2 10 11.3 0 1.1-.1 2-.2 2.5h-15.7zm10.2-4.1c0-2.2-.9-5.7-4.8-5.7-3.6 0-5.2 3.3-5.4 5.7z"/>
+<path d="m300.2 12.2v27.4c0 2.4.1 5 .2 6.5h-5.3l-.2-3.7h-.1c-1.4 2.6-4.2 4.2-7.6 4.2-5.5 0-9.9-4.7-9.9-11.9 0-7.8 4.8-12.4 10.4-12.4 3.2 0 5.5 1.3 6.5 3.1h.1v-13.2zm-5.9 20.4c0-.5 0-1.1-.1-1.5-.5-2.3-2.4-4.2-5.1-4.2-3.8 0-5.9 3.3-5.9 7.7 0 4.3 2.1 7.4 5.8 7.4 2.4 0 4.5-1.6 5.1-4.2.1-.5.2-1.1.2-1.7z"/>
+<path d="m338.5 34.3c0 8.6-6 12.4-12 12.4-6.6 0-11.7-4.5-11.7-12 0-7.6 5-12.3 12-12.3 7.1 0 11.7 4.8 11.7 11.9zm-17.5.3c0 4.5 2.2 7.9 5.8 7.9 3.3 0 5.7-3.3 5.7-8 0-3.6-1.6-7.8-5.6-7.8-4.3 0-5.9 4-5.9 7.9z"/>
+<path d="m343.3 29.8c0-2.7 0-4.9-.2-6.9h5.2l.3 3.5h.1c1-1.8 3.5-4 7.4-4 4.1 0 8.3 2.6 8.3 10v13.8h-5.9v-13.2c0-3.3-1.2-5.9-4.4-5.9-2.3 0-4 1.7-4.6 3.4-.2.5-.2 1.2-.2 1.9v13.7h-5.9v-16.3z"/>
+</svg>
index ce5c514e171c81dfc770cda9207be65227fb990b..1b429d48c9be5e3c4eacfb01091514bcacd496e1 100644 (file)
@@ -2319,6 +2319,20 @@ overview.complexity_tooltip.file={0} files have complexity around {1}
 
 overview.deprecated_profile=This quality profile uses {0} deprecated rules and should be updated.
 
+overview.badges.get_badge=Get project badges
+overview.badges.title=Badges
+overview.badges.description=Show the status of your project metrics on your README or website. Pick your style:
+overview.badges.metric=Metric
+overview.badges.options.colors.white=White
+overview.badges.options.colors.black=Black
+overview.badges.options.colors.orange=Orange
+overview.badges.measure.alt=Standard badge
+overview.badges.measure.description=This badge dynamically displays the current status of one metric of your project.
+overview.badges.marketing.alt=Scanned on SonarCloud badge
+overview.badges.marketing.description=This badge lets you advertise that you're using SonarCloud for code quality.
+overview.badges.quality_gate.alt=SonarCloud Quality Gate badge
+overview.badges.quality_gate.description=This badge dynamically displays the current quality gate status of your project.
+
 widget.as_calculated_on_x=As calculated on {0}