]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13426 Enable project badge for private project
authorMathieu Suen <mathieu.suen@sonarsource.com>
Wed, 10 Nov 2021 14:58:41 +0000 (15:58 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 16 Nov 2021 20:03:54 +0000 (20:03 +0000)
17 files changed:
server/sonar-web/src/main/js/api/project-badges.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/ProjectInformation-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/BadgeParams.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeButton-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/BadgeParams-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/utils-test.ts
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/utils.ts
server/sonar-web/src/main/js/components/common/CodeSnippet.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/CodeSnippet-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/api/project-badges.ts b/server/sonar-web/src/main/js/api/project-badges.ts
new file mode 100644 (file)
index 0000000..692b8d7
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import throwGlobalError from '../app/utils/throwGlobalError';
+import { getJSON } from '../helpers/request';
+
+export function getProjectBadgesToken(project: string) {
+  return getJSON('/api/project_badges/token', { project })
+    .then(({ token }) => token)
+    .catch(throwGlobalError);
+}
index 578bd2225cd6793270190008a2e8499214259112..0b2072f2d116132a7b85ae4c8031e531f6a5f913 100644 (file)
@@ -90,7 +90,6 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
       isLoggedIn(currentUser) && component.qualifier === ComponentQualifier.Project;
     const canUseBadges =
       metrics !== undefined &&
-      component.visibility !== 'private' &&
       (component.qualifier === ComponentQualifier.Application ||
         component.qualifier === ComponentQualifier.Project);
 
index 856efeeb83c294c4197b2da40b6103b4e980e2e7..059fbcba52047af77a93d6289cd5646538d4a218 100644 (file)
@@ -22,6 +22,8 @@ import * as React from 'react';
 import { mockComponent } from '../../../../../../helpers/mocks/component';
 import { mockCurrentUser, mockLoggedInUser, mockMetric } from '../../../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../../../helpers/testUtils';
+import { ComponentQualifier } from '../../../../../../types/component';
+import ProjectBadges from '../badges/ProjectBadges';
 import { ProjectInformation } from '../ProjectInformation';
 import { ProjectInformationPages } from '../ProjectInformationPages';
 
@@ -53,6 +55,17 @@ it('should handle page change', async () => {
   expect(wrapper.state().page).toBe(ProjectInformationPages.badges);
 });
 
+it('should display badge', () => {
+  const wrapper = shallowRender({
+    component: mockComponent({ qualifier: ComponentQualifier.Project })
+  });
+
+  expect(wrapper.find(ProjectBadges).type).toBeDefined();
+
+  wrapper.setProps({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
+  expect(wrapper.find(ProjectBadges).type).toBeDefined();
+});
+
 function shallowRender(props: Partial<ProjectInformation['props']> = {}) {
   return shallow<ProjectInformation>(
     <ProjectInformation
index 19177eb6608961170d308caf7cfd06608b72003b..fe5bdc421ce8076e4f1da711b92bded31e44fcf8 100644 (file)
@@ -203,7 +203,7 @@ exports[`should render correctly: private 1`] = `
 <Fragment>
   <Memo(ProjectInformationRenderer)
     canConfigureNotifications={false}
-    canUseBadges={false}
+    canUseBadges={true}
     component={
       Object {
         "breadcrumbs": Array [],
@@ -230,5 +230,24 @@ exports[`should render correctly: private 1`] = `
     onComponentChange={[MockFunction]}
     onPageChange={[Function]}
   />
+  <InfoDrawerPage
+    displayed={false}
+    onPageChange={[Function]}
+  >
+    <ProjectBadges
+      metrics={
+        Object {
+          "coverage": Object {
+            "id": "coverage",
+            "key": "coverage",
+            "name": "Coverage",
+            "type": "PERCENT",
+          },
+        }
+      }
+      project="my-project"
+      qualifier="TRK"
+    />
+  </InfoDrawerPage>
 </Fragment>
 `;
index 4aa6edc257e007a712a3a07b97f19ab569d26823..92a8e74dbb2e834339ce3016ec80d9555819d158 100644 (file)
@@ -22,7 +22,7 @@ import * as React from 'react';
 import { fetchWebApi } from '../../../../../../api/web-api';
 import Select from '../../../../../../components/controls/Select';
 import { getLocalizedMetricName, translate } from '../../../../../../helpers/l10n';
-import { BadgeColors, BadgeFormats, BadgeOptions, BadgeType } from './utils';
+import { BadgeFormats, BadgeOptions, BadgeType } from './utils';
 
 interface Props {
   className?: string;
@@ -90,10 +90,6 @@ export default class BadgeParams extends React.PureComponent<Props> {
     });
   };
 
-  handleColorChange = ({ value }: { value: BadgeColors }) => {
-    this.props.updateOptions({ color: value });
-  };
-
   handleFormatChange = ({ value }: { value: BadgeFormats }) => {
     this.props.updateOptions({ format: value });
   };
@@ -103,24 +99,7 @@ export default class BadgeParams extends React.PureComponent<Props> {
   };
 
   renderBadgeType = (type: BadgeType, options: BadgeOptions) => {
-    if (type === BadgeType.marketing) {
-      return (
-        <>
-          <label className="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}
-          />
-        </>
-      );
-    } else if (type === BadgeType.measure) {
+    if (type === BadgeType.measure) {
       return (
         <>
           <label className="spacer-right" htmlFor="badge-metric">
index f57119b9512627db4f6513f305fad4e184b65ce4..73bdf3fb9a1879a650ad81595a9fcc3d7c41a870 100644 (file)
@@ -18,7 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { getProjectBadgesToken } from '../../../../../../api/project-badges';
 import CodeSnippet from '../../../../../../components/common/CodeSnippet';
+import { Alert } from '../../../../../../components/ui/Alert';
 import { getBranchLikeQuery } from '../../../../../../helpers/branch-like';
 import { translate } from '../../../../../../helpers/l10n';
 import { BranchLike } from '../../../../../../types/branch-like';
@@ -36,16 +38,36 @@ interface Props {
 }
 
 interface State {
+  token: string;
   selectedType: BadgeType;
   badgeOptions: BadgeOptions;
 }
 
 export default class ProjectBadges extends React.PureComponent<Props, State> {
+  mounted = false;
   state: State = {
+    token: '',
     selectedType: BadgeType.measure,
-    badgeOptions: { color: 'white', metric: MetricKey.alert_status }
+    badgeOptions: { metric: MetricKey.alert_status }
   };
 
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchToken();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  async fetchToken() {
+    const { project } = this.props;
+    const token = await getProjectBadgesToken(project);
+    if (this.mounted) {
+      this.setState({ token });
+    }
+  }
+
   handleSelectBadge = (selectedType: BadgeType) => {
     this.setState({ selectedType });
   };
@@ -56,7 +78,7 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
 
   render() {
     const { branchLike, project, qualifier } = this.props;
-    const { selectedType, badgeOptions } = this.state;
+    const { selectedType, badgeOptions, token } = this.state;
     const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) };
 
     return (
@@ -67,7 +89,7 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
           onClick={this.handleSelectBadge}
           selected={BadgeType.measure === selectedType}
           type={BadgeType.measure}
-          url={getBadgeUrl(BadgeType.measure, fullBadgeOptions)}
+          url={getBadgeUrl(BadgeType.measure, fullBadgeOptions, token)}
         />
         <p className="huge-spacer-bottom spacer-top">
           {translate('overview.badges', BadgeType.measure, 'description', qualifier)}
@@ -76,7 +98,7 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
           onClick={this.handleSelectBadge}
           selected={BadgeType.qualityGate === selectedType}
           type={BadgeType.qualityGate}
-          url={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions)}
+          url={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token)}
         />
         <p className="huge-spacer-bottom spacer-top">
           {translate('overview.badges', BadgeType.qualityGate, 'description', qualifier)}
@@ -88,7 +110,11 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
           type={selectedType}
           updateOptions={this.handleUpdateOptions}
         />
-        <CodeSnippet isOneLine={true} snippet={getBadgeSnippet(selectedType, fullBadgeOptions)} />
+        <Alert variant="warning">{translate('overview.badges.leak_warning')}</Alert>
+        <CodeSnippet
+          isOneLine={true}
+          snippet={getBadgeSnippet(selectedType, fullBadgeOptions, token)}
+        />
       </div>
     );
   }
index 221e8c9dbd25011ce96e35d7d6e02626b6af4494..124d1cc72eb9c6cb3c1f7a146bbd0b5665c69395 100644 (file)
@@ -33,7 +33,7 @@ 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);
+  expect(onClick).toHaveBeenCalledWith(BadgeType.qualityGate);
 });
 
 function getWrapper(props = {}) {
@@ -41,7 +41,7 @@ function getWrapper(props = {}) {
     <BadgeButton
       onClick={jest.fn()}
       selected={false}
-      type={BadgeType.marketing}
+      type={BadgeType.qualityGate}
       url="http://foo.bar"
       {...props}
     />
index cf133f22256f8ff381f37072c712b0d095cfb826..696ec84f04fff2ca5a3f5a78233a839e3a1d9a9f 100644 (file)
@@ -42,14 +42,6 @@ const METRICS = {
   coverage: { key: 'coverage', name: 'Coverage' } as T.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 });
@@ -70,8 +62,8 @@ function getWrapper(props = {}) {
   return shallow(
     <BadgeParams
       metrics={METRICS}
-      options={{ color: 'white', metric: 'alert_status' }}
-      type={BadgeType.marketing}
+      options={{ metric: 'alert_status' }}
+      type={BadgeType.measure}
       updateOptions={jest.fn()}
       {...props}
     />
index 4f1c36803920a9219edf8504d5c58be8c1ef303a..8b0868d437c4b000b62713947000cb1aee86ace6 100644 (file)
@@ -21,6 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockBranch } from '../../../../../../../helpers/mocks/branch-like';
 import { mockMetric } from '../../../../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../../../../helpers/testUtils';
 import { Location } from '../../../../../../../helpers/urls';
 import { MetricKey } from '../../../../../../../types/metrics';
 import ProjectBadges from '../ProjectBadges';
@@ -31,8 +32,14 @@ jest.mock('../../../../../../../helpers/urls', () => ({
   getProjectUrl: () => ({ pathname: '/dashboard' } as Location)
 }));
 
-it('should display correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
+jest.mock('../../../../../../../api/project-badges', () => ({
+  getProjectBadgesToken: jest.fn().mockResolvedValue('foo')
+}));
+
+it('should display correctly', async () => {
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
 });
 
 function shallowRender(overrides = {}) {
index c1ea1a7eb507889ce8d9d2281da2a3c25d3cb79d..b30b2a01152c77a42758ce22f4bb723561525bd9 100644 (file)
@@ -6,7 +6,7 @@ exports[`should display correctly 1`] = `
   onClick={[Function]}
 >
   <img
-    alt="overview.badges.marketing.alt"
+    alt="overview.badges.quality_gate.alt"
     src="http://foo.bar"
     width="128px"
   />
@@ -19,7 +19,7 @@ exports[`should display correctly 2`] = `
   onClick={[Function]}
 >
   <img
-    alt="overview.badges.marketing.alt"
+    alt="overview.badges.quality_gate.alt"
     src="http://foo.bar"
     width="128px"
   />
index b6b76f5f6a3c226d7e38d43d60ccf469708f1748..704be60fbcce9d1cdb3f1dac4cf3bf453aef8eaa 100644 (file)
@@ -1,68 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should display marketing badge params 1`] = `
-<div>
-  <label
-    className="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"
-  />
-  <label
-    className="spacer-right spacer-top"
-    htmlFor="badge-format"
-  >
-    format
-    :
-  </label>
-  <Select
-    className="input-medium"
-    clearable={false}
-    name="badge-format"
-    onChange={[Function]}
-    options={
-      Array [
-        Object {
-          "label": "overview.badges.options.formats.md",
-          "value": "md",
-        },
-        Object {
-          "label": "overview.badges.options.formats.url",
-          "value": "url",
-        },
-      ]
-    }
-    searchable={false}
-    value="md"
-  />
-</div>
-`;
-
 exports[`should display measure badge params 1`] = `
 <div>
   <label
index c48fa415a2edad4ac9fa1922c3bddebf34e55d5e..4c7f8b850db66be1d2c545613b0f43c339e3af9f 100644 (file)
@@ -16,7 +16,7 @@ exports[`should display correctly 1`] = `
     onClick={[Function]}
     selected={true}
     type="measure"
-    url="host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status"
+    url="host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status&token=foo"
   />
   <p
     className="huge-spacer-bottom spacer-top"
@@ -27,7 +27,7 @@ exports[`should display correctly 1`] = `
     onClick={[Function]}
     selected={false}
     type="quality_gate"
-    url="host/api/project_badges/quality_gate?branch=branch-6.7&project=foo"
+    url="host/api/project_badges/quality_gate?branch=branch-6.7&project=foo&token=foo"
   />
   <p
     className="huge-spacer-bottom spacer-top"
@@ -54,16 +54,20 @@ exports[`should display correctly 1`] = `
     }
     options={
       Object {
-        "color": "white",
         "metric": "alert_status",
       }
     }
     type="measure"
     updateOptions={[Function]}
   />
+  <Alert
+    variant="warning"
+  >
+    overview.badges.leak_warning
+  </Alert>
   <CodeSnippet
     isOneLine={true}
-    snippet="[![alert_status](host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status)](/dashboard)"
+    snippet="[![alert_status](host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status&token=foo)](/dashboard)"
   />
 </div>
 `;
index 263fb541de57bfeabaa499dfa11a7e773280bf66..6b250af7b69391d51d455010e85c6c1688bc90c4 100644 (file)
@@ -29,44 +29,40 @@ jest.mock('../../../../../../../helpers/urls', () => ({
 
 const options: BadgeOptions = {
   branch: 'master',
-  color: 'white',
   metric: 'alert_status',
   project: 'foo'
 };
 
 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)).toBe(
-      'host/api/project_badges/quality_gate?branch=master&project=foo'
+    expect(getBadgeUrl(BadgeType.qualityGate, options, 'foo')).toBe(
+      'host/api/project_badges/quality_gate?branch=master&project=foo&token=foo'
     );
   });
 
   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'
+    expect(getBadgeUrl(BadgeType.measure, options, 'foo')).toBe(
+      'host/api/project_badges/measure?branch=master&project=foo&metric=alert_status&token=foo'
     );
   });
 
   it('should ignore undefined parameters', () => {
-    expect(getBadgeUrl(BadgeType.measure, { color: 'white', metric: 'alert_status' })).toBe(
-      'host/api/project_badges/measure?metric=alert_status'
+    expect(getBadgeUrl(BadgeType.measure, { metric: 'alert_status' }, 'foo')).toBe(
+      'host/api/project_badges/measure?metric=alert_status&token=foo'
+    );
+  });
+
+  it('should force metric parameters', () => {
+    expect(getBadgeUrl(BadgeType.measure, {}, 'foo')).toBe(
+      'host/api/project_badges/measure?metric=alert_status&token=foo'
     );
   });
 });
 
 describe('#getBadgeSnippet', () => {
   it('should generate a correct markdown image', () => {
-    expect(getBadgeSnippet(BadgeType.marketing, { ...options, format: 'md' })).toBe(
-      '[![SonarCloud](host/images/project_badges/sonarcloud-white.svg)](host/dashboard?id=foo&branch=master)'
+    expect(getBadgeSnippet(BadgeType.measure, { ...options, format: 'md' }, 'foo')).toBe(
+      '[![alert_status](host/api/project_badges/measure?branch=master&project=foo&metric=alert_status&token=foo)](host/dashboard?id=foo&branch=master)'
     );
   });
 });
index f4d4a8a7233f8513644cadf026252644a1072fe1..7191738922704e18c4ff99b8d696c270bf89e6a7 100644 (file)
@@ -26,7 +26,6 @@ export type BadgeFormats = 'md' | 'url';
 
 export interface BadgeOptions {
   branch?: string;
-  color?: BadgeColors;
   format?: BadgeFormats;
   project?: string;
   metric?: string;
@@ -35,12 +34,11 @@ export interface BadgeOptions {
 
 export enum BadgeType {
   measure = 'measure',
-  qualityGate = 'quality_gate',
-  marketing = 'marketing'
+  qualityGate = 'quality_gate'
 }
 
-export function getBadgeSnippet(type: BadgeType, options: BadgeOptions) {
-  const url = getBadgeUrl(type, options);
+export function getBadgeSnippet(type: BadgeType, options: BadgeOptions, token: string) {
+  const url = getBadgeUrl(type, options, token);
   const { branch, format = 'md', metric = 'alert_status', project } = options;
 
   if (format === 'url') {
@@ -50,9 +48,6 @@ export function getBadgeSnippet(type: BadgeType, options: BadgeOptions) {
     let projectUrl;
 
     switch (type) {
-      case BadgeType.marketing:
-        label = 'SonarCloud';
-        break;
       case BadgeType.measure:
         label = getLocalizedMetricName({ key: metric });
         break;
@@ -73,19 +68,18 @@ export function getBadgeSnippet(type: BadgeType, options: BadgeOptions) {
 
 export function getBadgeUrl(
   type: BadgeType,
-  { branch, project, color = 'white', metric = 'alert_status', pullRequest }: BadgeOptions
+  { branch, project, metric = 'alert_status', pullRequest }: BadgeOptions,
+  token: string
 ) {
   switch (type) {
-    case BadgeType.marketing:
-      return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`;
     case BadgeType.qualityGate:
       return `${getHostUrl()}/api/project_badges/quality_gate?${new URLSearchParams(
-        omitNil({ branch, project, pullRequest })
+        omitNil({ branch, project, pullRequest, token })
       ).toString()}`;
     case BadgeType.measure:
     default:
       return `${getHostUrl()}/api/project_badges/measure?${new URLSearchParams(
-        omitNil({ branch, project, metric, pullRequest })
+        omitNil({ branch, project, metric, pullRequest, token })
       ).toString()}`;
   }
 }
index 910cbbd877aaa423412dc0d6542f8156e876f02c..80ab7ff4baa005f943780af8d24a95b852c67c81 100644 (file)
@@ -42,7 +42,8 @@ export default function CodeSnippet(props: CodeSnippetProps) {
 
   return (
     <div className={classNames('code-snippet spacer-top spacer-bottom display-flex-row', {})}>
-      <pre className="flex-1" ref={snippetRef}>
+      {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
+      <pre className="flex-1" ref={snippetRef} tabIndex={0}>
         {finalSnippet}
       </pre>
       {!noCopy && <ClipboardButton copyValue={finalSnippet} />}
index f1901c905d061f9139110361c56db9f6a47b3066..f0a986e706d807d8460f67478774e7a7724fd4b9 100644 (file)
@@ -6,6 +6,7 @@ exports[`renders correctly: array snippet 1`] = `
 >
   <pre
     className="flex-1"
+    tabIndex={0}
   >
     foo \\
   bar
@@ -23,6 +24,7 @@ exports[`renders correctly: default 1`] = `
 >
   <pre
     className="flex-1"
+    tabIndex={0}
   >
     foo
 bar
@@ -40,6 +42,7 @@ exports[`renders correctly: no copy 1`] = `
 >
   <pre
     className="flex-1"
+    tabIndex={0}
   >
     foo
 bar
@@ -53,6 +56,7 @@ exports[`renders correctly: single line with array snippet 1`] = `
 >
   <pre
     className="flex-1"
+    tabIndex={0}
   >
     foo bar
   </pre>
index 145b388204fc5213a80734d1d709bd40c377f047..423495025c3fc9c8540065305997f76869a0f153 100644 (file)
@@ -3169,7 +3169,7 @@ overview.badges.quality_gate.description=Displays the current quality gate statu
 overview.badges.quality_gate.description.APP=Displays the current quality gate status of your application.
 overview.badges.quality_gate.description.TRK=Displays the current quality gate status of your project.
 overview.badges.quality_gate.description.VW=Displays the current quality gate status of your portfolio.
-
+overview.badges.leak_warning=Project badges can expose your security rating and other measures. Only use project badges in trusted environments.
 
 #------------------------------------------------------------------------------
 #