]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16206 Improve code sharing for security reports
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 4 May 2022 15:22:54 +0000 (17:22 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 5 May 2022 20:02:57 +0000 (20:02 +0000)
73 files changed:
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
server/sonar-web/src/main/js/app/components/__tests__/GlobalContainer-test.tsx
server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css [deleted file]
server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
server/sonar-web/src/main/js/apps/account/Account.tsx
server/sonar-web/src/main/js/apps/audit-logs/components/AuditAppRenderer.tsx
server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/components/App.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx
server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx
server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
server/sonar-web/src/main/js/apps/documentation/components/App.tsx
server/sonar-web/src/main/js/apps/groups/components/App.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/apps/marketplace/App.tsx
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx
server/sonar-web/src/main/js/apps/overview/components/App.tsx
server/sonar-web/src/main/js/apps/permission-templates/components/App.tsx
server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx
server/sonar-web/src/main/js/apps/projectQualityProfiles/ProjectQualityProfilesAppRenderer.tsx
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
server/sonar-web/src/main/js/apps/quality-profiles/components/QualityProfilesApp.tsx
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx
server/sonar-web/src/main/js/apps/system/components/App.tsx
server/sonar-web/src/main/js/apps/users/UsersApp.tsx
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx
server/sonar-web/src/main/js/apps/webhooks/components/App.tsx
server/sonar-web/src/main/js/components/a11y/A11yContext.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/__tests__/A11yProvider-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipLinks-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipTarget-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/Suggestions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsContext.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap [new file with mode: 0644]

index b02e8c98ebcf3e0f094ecd4d9c3425ac348ee24e..6bc280ca84f43a080d683054d788c96fcb22caa7 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import A11yProvider from '../../components/a11y/A11yProvider';
+import A11ySkipLinks from '../../components/a11y/A11ySkipLinks';
+import SuggestionsProvider from '../../components/embed-docs-modal/SuggestionsProvider';
 import Workspace from '../../components/workspace/Workspace';
-import A11yProvider from './a11y/A11yProvider';
-import A11ySkipLinks from './a11y/A11ySkipLinks';
 import BranchStatusContextProvider from './branch-status/BranchStatusContextProvider';
-import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider';
 import GlobalFooter from './GlobalFooter';
 import IndexationContextProvider from './indexation/IndexationContextProvider';
 import IndexationNotification from './indexation/IndexationNotification';
index 6f150e21f8cfa0519af56e0de15ec59a7582dcbd..b806154db88ad3574c0cdce6cc2b1ca86328d196 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import { BranchLike } from '../../types/branch-like';
 import { Component } from '../../types/types';
 import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-import A11ySkipTarget from './a11y/A11ySkipTarget';
 
 interface Props {
   children: JSX.Element;
index 6643512ad525697b11a3a6314bf8ff1de420d35e..e094b6512df0b1d017af24e543817a4047e71930 100644 (file)
@@ -22,7 +22,7 @@ import * as React from 'react';
 import { mockLocation } from '../../../helpers/testMocks';
 import GlobalContainer, { Props } from '../GlobalContainer';
 
-jest.mock('../embed-docs-modal/SuggestionsProvider', () => {
+jest.mock('../../../components/embed-docs-modal/SuggestionsProvider', () => {
   class SuggestionsProvider extends React.Component {
     render() {
       return this.props.children;
diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx
deleted file mode 100644 (file)
index 345b9ca..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { createContext } from 'react';
-import { A11ySkipLink } from '../../../types/types';
-
-export interface A11yContextShape {
-  addA11ySkipLink: (link: A11ySkipLink) => void;
-  removeA11ySkipLink: (link: A11ySkipLink) => void;
-  links: A11ySkipLink[];
-}
-
-export const A11yContext = createContext<A11yContextShape>({
-  addA11ySkipLink: () => {},
-  removeA11ySkipLink: () => {},
-  links: []
-});
diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx
deleted file mode 100644 (file)
index 5990692..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { sortBy } from 'lodash';
-import * as React from 'react';
-import { A11ySkipLink } from '../../../types/types';
-import { A11yContext } from './A11yContext';
-
-interface State {
-  links: A11ySkipLink[];
-}
-
-export default class A11yProvider extends React.Component<{}, State> {
-  keys: string[] = [];
-  state: State = { links: [] };
-
-  addA11ySkipLink = (link: A11ySkipLink) => {
-    this.setState(prevState => {
-      const links = [...prevState.links];
-      links.push({ ...link, weight: link.weight || 0 });
-      return { links };
-    });
-  };
-
-  removeA11ySkipLink = (link: A11ySkipLink) => {
-    this.setState(prevState => {
-      const links = prevState.links.filter(l => l.key !== link.key);
-      return { links };
-    });
-  };
-
-  render() {
-    const links = sortBy(this.state.links, 'weight');
-    return (
-      <A11yContext.Provider
-        value={{
-          addA11ySkipLink: this.addA11ySkipLink,
-          links,
-          removeA11ySkipLink: this.removeA11ySkipLink
-        }}>
-        {this.props.children}
-      </A11yContext.Provider>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css
deleted file mode 100644 (file)
index e1eabb6..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.
- */
-.a11y-skip-link {
-  width: 0;
-  height: 0;
-  padding: var(--gridSize);
-  position: absolute;
-  left: -9999px;
-  top: -9999px;
-  border: 0;
-  font-size: 1rem;
-  text-align: center;
-  z-index: 999;
-}
-
-.a11y-skip-link:focus {
-  width: auto;
-  height: auto;
-  left: 6px;
-  top: 6px;
-  color: white;
-  background-color: var(--globalNavBarBg);
-  text-decoration: underline;
-}
diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx
deleted file mode 100644 (file)
index 8af879b..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { A11yContext } from './A11yContext';
-import './A11ySkipLinks.css';
-
-export default function A11ySkipLinks() {
-  return (
-    <A11yContext.Consumer>
-      {({ links }) =>
-        links.map(link => (
-          <a className="a11y-skip-link" href={`#a11y_target__${link.key}`} key={link.key}>
-            {link.label}
-          </a>
-        ))
-      }
-    </A11yContext.Consumer>
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx
deleted file mode 100644 (file)
index 3cd3b14..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { translate } from '../../../helpers/l10n';
-import { A11ySkipLink } from '../../../types/types';
-import { A11yContext } from './A11yContext';
-
-interface Props {
-  anchor: string;
-  label?: string;
-  weight?: number;
-}
-
-export default function A11ySkipTarget(props: Props) {
-  return (
-    <A11yContext.Consumer>
-      {({ addA11ySkipLink, removeA11ySkipLink }) => (
-        <A11ySkipTargetInner
-          addA11ySkipLink={addA11ySkipLink}
-          removeA11ySkipLink={removeA11ySkipLink}
-          {...props}
-        />
-      )}
-    </A11yContext.Consumer>
-  );
-}
-
-interface InnerProps {
-  addA11ySkipLink: (link: A11ySkipLink) => void;
-  removeA11ySkipLink: (link: A11ySkipLink) => void;
-}
-
-export class A11ySkipTargetInner extends React.PureComponent<Props & InnerProps> {
-  componentDidMount() {
-    this.props.addA11ySkipLink(this.getLink());
-  }
-
-  componentWillUnmount() {
-    this.props.removeA11ySkipLink(this.getLink());
-  }
-
-  getLink = (): A11ySkipLink => {
-    const { anchor: key, label = translate('skip_to_content'), weight } = this.props;
-    return { key, label, weight };
-  };
-
-  render() {
-    const { anchor } = this.props;
-    return <span id={`a11y_target__${anchor}`} />;
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx
deleted file mode 100644 (file)
index 70a6dc4..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import { A11yContextShape } from '../A11yContext';
-import A11yProvider from '../A11yProvider';
-
-const link1 = { key: 'link1', label: 'Link 1', weight: 0 };
-const link2 = { key: 'link2', label: 'Link 2', weight: -10 };
-const link3 = { key: 'link3', label: 'Link 3', weight: 0 };
-
-it('should allow to register new skip links', () => {
-  const wrapper = shallowRender();
-  const instance = wrapper.instance();
-  expect(wrapper.state('links')).toEqual([]);
-
-  // Check that an absence of weight is treated as "0".
-  instance.addA11ySkipLink({ ...link1, weight: undefined });
-  expect(wrapper.state('links')).toEqual([link1]);
-
-  instance.addA11ySkipLink(link2);
-  expect(wrapper.state('links')).toEqual([link1, link2]);
-});
-
-it('should pass the ordered links to the consumers', () => {
-  const wrapper = shallowRender();
-  const instance = wrapper.instance();
-  instance.setState({ links: [link1, link2, link3] });
-  waitAndUpdate(wrapper);
-  expect((wrapper.prop('value') as A11yContextShape).links).toEqual([link2, link1, link3]);
-});
-
-it('should allow to unregister skip links', () => {
-  const wrapper = shallowRender();
-  const instance = wrapper.instance();
-  instance.setState({ links: [link1, link2, link3] });
-
-  instance.removeA11ySkipLink(link1);
-  expect(wrapper.state('links')).toEqual([link2, link3]);
-
-  instance.removeA11ySkipLink(link2);
-  expect(wrapper.state('links')).toEqual([link3]);
-});
-
-function shallowRender() {
-  return shallow<A11yProvider>(
-    <A11yProvider>
-      <div />
-    </A11yProvider>
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx
deleted file mode 100644 (file)
index f19805d..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import A11ySkipLinks from '../A11ySkipLinks';
-
-jest.mock('../A11yContext', () => ({
-  A11yContext: {
-    Consumer: ({ children }: any) => {
-      return children({
-        links: [
-          { key: 'link1', label: 'Label 1' },
-          { key: 'link2', label: 'Label 2' }
-        ]
-      });
-    }
-  }
-}));
-
-it('should render correctly', () => {
-  expect(shallow(<A11ySkipLinks />).dive()).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx
deleted file mode 100644 (file)
index 55083e6..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { mount } from 'enzyme';
-import * as React from 'react';
-import { A11ySkipTargetInner } from '../A11ySkipTarget';
-
-it('should render correctly, and (un)register the link when (un)mounted', () => {
-  const link = { key: 'main', label: 'Skip to content' };
-  const addA11ySkipLink = jest.fn();
-  const removeA11ySkipLink = jest.fn();
-  const wrapper = mount(
-    <A11ySkipTargetInner
-      addA11ySkipLink={addA11ySkipLink}
-      anchor={link.key}
-      label={link.label}
-      removeA11ySkipLink={removeA11ySkipLink}
-    />
-  );
-
-  expect(wrapper).toMatchSnapshot();
-  expect(addA11ySkipLink).toBeCalledWith(link);
-  wrapper.unmount();
-  expect(removeA11ySkipLink).toBeCalledWith(link);
-});
diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap
deleted file mode 100644 (file)
index a8a148a..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-Array [
-  <a
-    className="a11y-skip-link"
-    href="#a11y_target__link1"
-    key="link1"
-  >
-    Label 1
-  </a>,
-  <a
-    className="a11y-skip-link"
-    href="#a11y_target__link2"
-    key="link2"
-  >
-    Label 2
-  </a>,
-]
-`;
diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap
deleted file mode 100644 (file)
index 8fa4730..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly, and (un)register the link when (un)mounted 1`] = `
-<A11ySkipTargetInner
-  addA11ySkipLink={
-    [MockFunction] {
-      "calls": Array [
-        Array [
-          Object {
-            "key": "main",
-            "label": "Skip to content",
-            "weight": undefined,
-          },
-        ],
-      ],
-      "results": Array [
-        Object {
-          "type": "return",
-          "value": undefined,
-        },
-      ],
-    }
-  }
-  anchor="main"
-  label="Skip to content"
-  removeA11ySkipLink={[MockFunction]}
->
-  <span
-    id="a11y_target__main"
-  />
-</A11ySkipTargetInner>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
deleted file mode 100644 (file)
index 1a2b64a..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { Link } from 'react-router';
-import { DropdownOverlay } from '../../../components/controls/Dropdown';
-import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
-import { SuggestionLink } from '../../../types/types';
-import { SuggestionsContext } from './SuggestionsContext';
-
-interface Props {
-  onClose: () => void;
-}
-
-export default class EmbedDocsPopup extends React.PureComponent<Props> {
-  renderTitle(text: string) {
-    return <li className="menu-header">{text}</li>;
-  }
-
-  renderSuggestions = ({ suggestions }: { suggestions: SuggestionLink[] }) => {
-    if (suggestions.length === 0) {
-      return null;
-    }
-    return (
-      <>
-        {this.renderTitle(translate('embed_docs.suggestion'))}
-        {suggestions.map((suggestion, index) => (
-          <li key={index}>
-            <Link onClick={this.props.onClose} target="_blank" to={suggestion.link}>
-              {suggestion.text}
-            </Link>
-          </li>
-        ))}
-        <li className="divider" />
-      </>
-    );
-  };
-
-  renderIconLink(link: string, icon: string, text: string) {
-    return (
-      <a href={link} rel="noopener noreferrer" target="_blank">
-        <img
-          alt={text}
-          className="spacer-right"
-          height="18"
-          src={`${getBaseUrl()}/images/${icon}`}
-          width="18"
-        />
-        {text}
-      </a>
-    );
-  }
-
-  render() {
-    return (
-      <DropdownOverlay>
-        <ul className="menu abs-width-240">
-          <SuggestionsContext.Consumer>{this.renderSuggestions}</SuggestionsContext.Consumer>
-          <li>
-            <Link onClick={this.props.onClose} target="_blank" to="/documentation">
-              {translate('embed_docs.documentation')}
-            </Link>
-          </li>
-          <li>
-            <Link onClick={this.props.onClose} to="/web_api">
-              {translate('api_documentation.page')}
-            </Link>
-          </li>
-          <li className="divider" />
-          <li>
-            <a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank">
-              {translate('embed_docs.get_help')}
-            </a>
-          </li>
-          <li className="divider" />
-          {this.renderTitle(translate('embed_docs.stay_connected'))}
-          <li>
-            {this.renderIconLink(
-              'https://www.sonarqube.org/whats-new/?referrer=sonarqube',
-              'embed-doc/sq-icon.svg',
-              translate('embed_docs.news')
-            )}
-          </li>
-          <li>
-            {this.renderIconLink(
-              'https://twitter.com/SonarQube',
-              'embed-doc/twitter-icon.svg',
-              'Twitter'
-            )}
-          </li>
-        </ul>
-      </DropdownOverlay>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
deleted file mode 100644 (file)
index e0249a6..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { ButtonLink } from '../../../components/controls/buttons';
-import Toggler from '../../../components/controls/Toggler';
-import HelpIcon from '../../../components/icons/HelpIcon';
-import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
-import { translate } from '../../../helpers/l10n';
-
-const EmbedDocsPopup = lazyLoadComponent(() => import('./EmbedDocsPopup'));
-
-interface State {
-  helpOpen: boolean;
-}
-
-export default class EmbedDocsPopupHelper extends React.PureComponent<{}, State> {
-  mounted = false;
-  state: State = { helpOpen: false };
-
-  setHelpDisplay = (helpOpen: boolean) => {
-    this.setState({ helpOpen });
-  };
-
-  handleClick = () => {
-    this.toggleHelp();
-  };
-
-  toggleHelp = () => {
-    this.setState(state => {
-      return { helpOpen: !state.helpOpen };
-    });
-  };
-
-  closeHelp = () => {
-    this.setState({ helpOpen: false });
-  };
-
-  render() {
-    return (
-      <li className="dropdown">
-        <Toggler
-          onRequestClose={this.closeHelp}
-          open={this.state.helpOpen}
-          overlay={<EmbedDocsPopup onClose={this.closeHelp} />}>
-          <ButtonLink
-            aria-expanded={this.state.helpOpen}
-            aria-haspopup={true}
-            className="navbar-help navbar-icon"
-            onClick={this.handleClick}
-            title={translate('help')}>
-            <HelpIcon />
-          </ButtonLink>
-        </Toggler>
-      </li>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx
deleted file mode 100644 (file)
index 9cf36ba..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { SuggestionsContext } from './SuggestionsContext';
-
-interface Props {
-  suggestions: string;
-}
-
-export default function Suggestions({ suggestions }: Props) {
-  return (
-    <SuggestionsContext.Consumer>
-      {({ addSuggestions, removeSuggestions }) => (
-        <SuggestionsInner
-          addSuggestions={addSuggestions}
-          removeSuggestions={removeSuggestions}
-          suggestions={suggestions}
-        />
-      )}
-    </SuggestionsContext.Consumer>
-  );
-}
-
-interface SuggestionsInnerProps {
-  addSuggestions: (key: string) => void;
-  removeSuggestions: (key: string) => void;
-  suggestions: string;
-}
-
-class SuggestionsInner extends React.PureComponent<SuggestionsInnerProps> {
-  componentDidMount() {
-    this.props.addSuggestions(this.props.suggestions);
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.suggestions !== this.props.suggestions) {
-      this.props.removeSuggestions(this.props.suggestions);
-      this.props.addSuggestions(prevProps.suggestions);
-    }
-  }
-
-  componentWillUnmount() {
-    this.props.removeSuggestions(this.props.suggestions);
-  }
-
-  render() {
-    return null;
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
deleted file mode 100644 (file)
index fcb5c85..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { createContext } from 'react';
-import { SuggestionLink } from '../../../types/types';
-
-interface SuggestionsContextShape {
-  addSuggestions: (key: string) => void;
-  removeSuggestions: (key: string) => void;
-  suggestions: SuggestionLink[];
-}
-
-export const SuggestionsContext = createContext<SuggestionsContextShape>({
-  addSuggestions: () => {},
-  removeSuggestions: () => {},
-  suggestions: []
-});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
deleted file mode 100644 (file)
index ffc6788..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
-import * as React from 'react';
-import { isSonarCloud } from '../../../helpers/system';
-import { Dict, SuggestionLink } from '../../../types/types';
-import { SuggestionsContext } from './SuggestionsContext';
-
-type SuggestionsJson = Dict<SuggestionLink[]>;
-
-interface State {
-  suggestions: SuggestionLink[];
-}
-
-export default class SuggestionsProvider extends React.Component<{}, State> {
-  keys: string[] = [];
-  state: State = { suggestions: [] };
-
-  fetchSuggestions = () => {
-    const jsonList = suggestionsJson as SuggestionsJson;
-    let suggestions: SuggestionLink[] = [];
-    this.keys.forEach(key => {
-      if (jsonList[key]) {
-        suggestions = [...jsonList[key], ...suggestions];
-      }
-    });
-    if (!isSonarCloud()) {
-      suggestions = suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
-    }
-    this.setState({ suggestions });
-  };
-
-  addSuggestions = (newKey: string) => {
-    this.keys = [...this.keys, newKey];
-    this.fetchSuggestions();
-  };
-
-  removeSuggestions = (oldKey: string) => {
-    this.keys = this.keys.filter(key => key !== oldKey);
-    this.fetchSuggestions();
-  };
-
-  render() {
-    return (
-      <SuggestionsContext.Provider
-        value={{
-          addSuggestions: this.addSuggestions,
-          removeSuggestions: this.removeSuggestions,
-          suggestions: this.state.suggestions
-        }}>
-        {this.props.children}
-      </SuggestionsContext.Provider>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
deleted file mode 100644 (file)
index f12348e..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import EmbedDocsPopup from '../EmbedDocsPopup';
-
-it('should render', () => {
-  const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} />);
-  expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
deleted file mode 100644 (file)
index 194ee3d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
-import * as React from 'react';
-import { isSonarCloud } from '../../../../helpers/system';
-import SuggestionsProvider from '../SuggestionsProvider';
-
-jest.mock(
-  'Docs/EmbedDocsSuggestions.json',
-  () => ({
-    pageA: [
-      { link: '/foo', text: 'Foo' },
-      { link: '/bar', text: 'Bar', scope: 'sonarcloud' }
-    ],
-    pageB: [{ link: '/qux', text: 'Qux' }]
-  }),
-  { virtual: true }
-);
-
-jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
-
-it('should add & remove suggestions', () => {
-  (isSonarCloud as jest.Mock).mockReturnValue(false);
-  const wrapper = shallow<SuggestionsProvider>(
-    <SuggestionsProvider>
-      <div />
-    </SuggestionsProvider>
-  );
-  const instance = wrapper.instance();
-  expect(wrapper.state('suggestions')).toEqual([]);
-
-  instance.addSuggestions('pageA');
-  expect(wrapper.state('suggestions')).toEqual([{ link: '/foo', text: 'Foo' }]);
-
-  instance.addSuggestions('pageB');
-  expect(wrapper.state('suggestions')).toEqual([
-    { link: '/qux', text: 'Qux' },
-    { link: '/foo', text: 'Foo' }
-  ]);
-
-  instance.removeSuggestions('pageA');
-  expect(wrapper.state('suggestions')).toEqual([{ link: '/qux', text: 'Qux' }]);
-});
-
-it('should show sonarcloud pages', () => {
-  (isSonarCloud as jest.Mock).mockReturnValue(true);
-  const wrapper = shallow<SuggestionsProvider>(
-    <SuggestionsProvider>
-      <div />
-    </SuggestionsProvider>
-  );
-  const instance = wrapper.instance();
-  expect(wrapper.state('suggestions')).toEqual([]);
-
-  instance.addSuggestions('pageA');
-  expect(wrapper.state('suggestions')).toEqual([
-    { link: '/foo', text: 'Foo' },
-    { link: '/bar', text: 'Bar', scope: 'sonarcloud' }
-  ]);
-});
diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
deleted file mode 100644 (file)
index 29a379a..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<DropdownOverlay>
-  <ul
-    className="menu abs-width-240"
-  >
-    <ContextConsumer>
-      <Component />
-    </ContextConsumer>
-    <li>
-      <Link
-        onClick={[MockFunction]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        target="_blank"
-        to="/documentation"
-      >
-        embed_docs.documentation
-      </Link>
-    </li>
-    <li>
-      <Link
-        onClick={[MockFunction]}
-        onlyActiveOnIndex={false}
-        style={Object {}}
-        to="/web_api"
-      >
-        api_documentation.page
-      </Link>
-    </li>
-    <li
-      className="divider"
-    />
-    <li>
-      <a
-        href="https://community.sonarsource.com/"
-        rel="noopener noreferrer"
-        target="_blank"
-      >
-        embed_docs.get_help
-      </a>
-    </li>
-    <li
-      className="divider"
-    />
-    <li
-      className="menu-header"
-    >
-      embed_docs.stay_connected
-    </li>
-    <li>
-      <a
-        href="https://www.sonarqube.org/whats-new/?referrer=sonarqube"
-        rel="noopener noreferrer"
-        target="_blank"
-      >
-        <img
-          alt="embed_docs.news"
-          className="spacer-right"
-          height="18"
-          src="/images/embed-doc/sq-icon.svg"
-          width="18"
-        />
-        embed_docs.news
-      </a>
-    </li>
-    <li>
-      <a
-        href="https://twitter.com/SonarQube"
-        rel="noopener noreferrer"
-        target="_blank"
-      >
-        <img
-          alt="Twitter"
-          className="spacer-right"
-          height="18"
-          src="/images/embed-doc/twitter-icon.svg"
-          width="18"
-        />
-        Twitter
-      </a>
-    </li>
-  </ul>
-</DropdownOverlay>
-`;
index b534527ce4f3f398e0a17f3b33e5b75c96f67c78..98ee13683b4fb12d4b466c89482ebc81c295d431 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { FormattedMessage } from 'react-intl';
 import NotFound from '../../../app/components/NotFound';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import DonutChart from '../../../components/charts/DonutChart';
 import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
 import BoxedTabs from '../../../components/controls/BoxedTabs';
@@ -45,6 +46,7 @@ import Select, { SearchSelect } from '../../../components/controls/Select';
 import SelectList, { SelectListFilter } from '../../../components/controls/SelectList';
 import SimpleModal from '../../../components/controls/SimpleModal';
 import Tooltip from '../../../components/controls/Tooltip';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import AlertErrorIcon from '../../../components/icons/AlertErrorIcon';
 import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
 import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
@@ -118,8 +120,6 @@ import {
   getMeasureHistoryUrl,
   getRulesUrl
 } from '../../../helpers/urls';
-import A11ySkipTarget from '../a11y/A11ySkipTarget';
-import Suggestions from '../embed-docs-modal/Suggestions';
 
 const exposeLibraries = () => {
   const global = window as any;
index 0c7b6d964ccdf6442429cf04c5681f6998c8943c..6dd85f5fde48249ea591b890adc12416e7c17952 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import EmbedDocsPopupHelper from '../../../../components/embed-docs-modal/EmbedDocsPopupHelper';
 import NavBar from '../../../../components/ui/NavBar';
 import { CurrentUser } from '../../../../types/users';
 import { rawSizes } from '../../../theme';
 import withCurrentUserContext from '../../current-user/withCurrentUserContext';
-import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper';
 import Search from '../../search/Search';
 import './GlobalNav.css';
 import GlobalNavBranding from './GlobalNavBranding';
index 32a5118846689c61266a20137e8824cd1530ba39..1a7b5fcd4ebabe8fc8f12b6a08780f135b300844 100644 (file)
@@ -19,9 +19,9 @@
  */
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import handleRequiredAuthentication from '../../helpers/handleRequiredAuthentication';
 import { translate } from '../../helpers/l10n';
 import { CurrentUser, LoggedInUser } from '../../types/users';
index a89dbc8b53c7e8c734d2e3767dfa029bf2627e4f..c8f087b80c35443941cf16e7db30f06234759f26 100644 (file)
@@ -22,9 +22,9 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import DateRangeInput from '../../../components/controls/DateRangeInput';
 import Radio from '../../../components/controls/Radio';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import '../style.css';
 import { HousekeepingPolicy, now, RangeOption } from '../utils';
index f70fdc81ecfae14bc3b89d93e6dd46d7a0f8c0de..82bc95ab111b7251b83214287f1db4d303d4cb7b 100644 (file)
@@ -27,7 +27,7 @@ import {
   getStatus,
   getTypes
 } from '../../../api/ce';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, Router } from '../../../components/hoc/withRouter';
 import { toShortNotSoISOString } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
index c6e556793570bbde6ba5ee16461ec6bf0b79e1ce..69481261394b68d60114c8773befc9f835a4cf8c 100644 (file)
@@ -25,12 +25,12 @@ import { debounce, intersection } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { InjectedRouter } from 'react-router';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import ListFooter from '../../../components/controls/ListFooter';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Alert } from '../../../components/ui/Alert';
 import { isPullRequest, isSameBranchLike } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
index 5c62cdb44638ef0ce0f52d8238593ffbb7a74088..5e052bab42cb57679d62decd99f53c359035886a 100644 (file)
@@ -23,13 +23,13 @@ import { Helmet } from 'react-helmet-async';
 import { withRouter, WithRouterProps } from 'react-router';
 import { Profile, searchQualityProfiles } from '../../../api/quality-profiles';
 import { getRulesApp, searchRules } from '../../../api/rules';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import FiltersHeader from '../../../components/common/FiltersHeader';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import ListFooter from '../../../components/controls/ListFooter';
 import SearchBox from '../../../components/controls/SearchBox';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import BackIcon from '../../../components/icons/BackIcon';
 import '../../../components/search-navigator.css';
 import { translate } from '../../../helpers/l10n';
index 422ef0a65ef01c059dd462fcf52750eb032181a2..c44ff041d5fde632c750de03d9e9e9474fbf3c60 100644 (file)
@@ -26,9 +26,9 @@ import { withRouter, WithRouterProps } from 'react-router';
 import { getMeasuresWithPeriod } from '../../../api/measures';
 import { getAllMetrics } from '../../../api/metrics';
 import withBranchStatusActions from '../../../app/components/branch-status/withBranchStatusActions';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { enhanceMeasure } from '../../../components/measure/utils';
 import '../../../components/search-navigator.css';
 import { Alert } from '../../../components/ui/Alert';
index de90a337509e94794cfa5f87aab5f9b15f1a0f31..37abf52ec711323227fcb9adc400532ebf749b9f 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import { InjectedRouter } from 'react-router';
 import { getComponentTree } from '../../../api/components';
 import { getMeasures } from '../../../api/measures';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
 import PageActions from '../../../components/ui/PageActions';
 import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
index 466423cc0ad23dacdd912bc03bbb8fc21b4f5ecd..8d04180dc07ea6b4da930deae339627e708fe710 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import { getComponentLeaves } from '../../../api/components';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import SourceViewer from '../../../components/SourceViewer/SourceViewer';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import PageActions from '../../../components/ui/PageActions';
index 0fa48324dc7d01212d2a6b7cf57c3a4522708205..ce346568958375f8ffcccd1d38f2c41ece534108 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { translate } from '../../../helpers/l10n';
 import { Dict, MeasureEnhanced } from '../../../types/types';
 import { groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils';
index c7e1c62d258353c0ee3f0fd8e0bf3e77bce450fd..1c793becabc4da158bb2214e194aa29d36356afd 100644 (file)
@@ -21,8 +21,8 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { WithRouterProps } from 'react-router';
 import { getAlmSettings } from '../../../api/alm-settings';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { translate } from '../../../helpers/l10n';
 import { getProjectUrl } from '../../../helpers/urls';
 import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
index 9c84026042160be0bd3ebb79b43f143e7988960d..a594ce38deca95c93f146f6196d39a68666462c3 100644 (file)
@@ -24,8 +24,8 @@ import { Helmet } from 'react-helmet-async';
 import { Link } from 'react-router';
 import { getInstalledPlugins } from '../../../api/plugins';
 import { getPluginStaticFileContent } from '../../../api/static';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import NotFound from '../../../app/components/NotFound';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
index 31cf74682afe37dffa10d6667acf467ecf2a011b..ac751d235a8b4f43f81a4048ab338637a30ccafb 100644 (file)
@@ -20,9 +20,9 @@
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import ListFooter from '../../../components/controls/ListFooter';
 import SearchBox from '../../../components/controls/SearchBox';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { omitNil } from '../../../helpers/request';
 import { Group, Paging } from '../../../types/types';
index 543bf234d68dfe01246d891dd702ddc3619d6921..cf030276b8a1bdaa39a384797bad9c052e7aabec 100644 (file)
@@ -23,8 +23,7 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
 import { searchIssues } from '../../../api/issues';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import EmptySearch from '../../../components/common/EmptySearch';
 import FiltersHeader from '../../../components/common/FiltersHeader';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
@@ -32,6 +31,7 @@ import { Button } from '../../../components/controls/buttons';
 import Checkbox from '../../../components/controls/Checkbox';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
 import ListFooter from '../../../components/controls/ListFooter';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, Router } from '../../../components/hoc/withRouter';
 import '../../../components/search-navigator.css';
 import { Alert } from '../../../components/ui/Alert';
index d7f470d2cfd64380b99c0866f8ce1ec3499bd459..65076bc8a40574eec488a09f46cd02461c0f7298 100644 (file)
@@ -29,7 +29,7 @@ import {
   getPluginUpdates
 } from '../../api/plugins';
 import { getValues, setSimpleSettingValue } from '../../api/settings';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { Location, Router, withRouter } from '../../components/hoc/withRouter';
 import { Alert } from '../../components/ui/Alert';
 import DeferredSpinner from '../../components/ui/DeferredSpinner';
index a041e1f7cf5683378c1d063194e6bc04d8584bd5..e55f0fd8b7b8a6a12e0fb917ba7c5b479c29a8e6 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import { parseDate } from '../../../helpers/dates';
 import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
 import { ApplicationPeriod } from '../../../types/application';
index 192ecf0eaa54548d52415797ebc99c946698dd03..91c19c67eb34ac7c365fae2f8e5071536a558516 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { lazyLoadComponent } from '../../../components/lazyLoadComponent';
 import { isPullRequest } from '../../../helpers/branch-like';
 import { ProjectAlmBindingResponse } from '../../../types/alm-settings';
index 178579337eb6f59cdd239e49933091a5c9be7355..7e16f3c2522e3dbcf79930dc1ce3c9295eb6b8db 100644 (file)
@@ -22,7 +22,7 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { getPermissionTemplates } from '../../../api/permissions';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { AppState } from '../../../types/appstate';
 import { Permission, PermissionTemplate } from '../../../types/types';
index 60d89d36cb0103cb6f5d201ca8038a587e2bc33a..127c324562a46509b78472f8db8c707e4c65eb31 100644 (file)
@@ -21,7 +21,7 @@ import { without } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import * as api from '../../../../api/permissions';
-import Suggestions from '../../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../../helpers/l10n';
 import { Paging, PermissionGroup, PermissionUser } from '../../../../types/types';
 import '../../styles.css';
index cfa8707d4663cc0da40c86e6fee9bdf9b023214d..b1fa4fa47ad51bbba1a28a7cb1e7b26bacff343f 100644 (file)
@@ -19,8 +19,8 @@
  */
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { parseDate } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
 import { MeasureHistory } from '../../../types/project-activity';
index 4c123c72f03d6dfeb36a5ae08653cf5541a94f4d..5e28f80a41d4ef999036237772fb1260e4c94fe6 100644 (file)
@@ -22,7 +22,7 @@ import { debounce } from 'lodash';
 import * as React from 'react';
 import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { isBranch, sortBranches } from '../../../helpers/branch-like';
index d765f530a84e1b8381bb36d652a77e657426fd89..9c211b81b73309bb855fe22fe77a30c9a9e5c876 100644 (file)
@@ -22,13 +22,13 @@ import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router';
 import { components, OptionProps } from 'react-select';
-import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import DisableableSelectOption from '../../components/common/DisableableSelectOption';
 import { SubmitButton } from '../../components/controls/buttons';
 import HelpTooltip from '../../components/controls/HelpTooltip';
 import Radio from '../../components/controls/Radio';
 import Select, { BasicSelectOption } from '../../components/controls/Select';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { Alert } from '../../components/ui/Alert';
 import { translate } from '../../helpers/l10n';
 import { isDiffMetric } from '../../helpers/measures';
index 0d61bf57fcbc3786441b491d2992b6bc6213f70d..28445dfe0e349567f40e7682a694f25d7a0135bf 100644 (file)
@@ -22,10 +22,10 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { Link } from 'react-router';
 import { Profile } from '../../api/quality-profiles';
-import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import { Button } from '../../components/controls/buttons';
 import HelpTooltip from '../../components/controls/HelpTooltip';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import EditIcon from '../../components/icons/EditIcon';
 import PlusCircleIcon from '../../components/icons/PlusCircleIcon';
 import { translate } from '../../helpers/l10n';
index 202a721c91627810a0c106a489b1d7b5f3f37972..a60da0bc7b4d34460ea9f01a10d348fa9e1f1a54 100644 (file)
 import { omitBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
 import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
 import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
 import ListFooter from '../../../components/controls/ListFooter';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import '../../../components/search-navigator.css';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
index 3f489112b8be44c40e4e7937c914e0611f5a6bde..020448c3f73ec8b923296d4879cf69c4b0fd9ac8 100644 (file)
@@ -24,8 +24,8 @@ import { getComponents, Project } from '../../api/components';
 import { changeProjectDefaultVisibility } from '../../api/permissions';
 import { getValues } from '../../api/settings';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
 import ListFooter from '../../components/controls/ListFooter';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { toShortNotSoISOString } from '../../helpers/dates';
 import { translate } from '../../helpers/l10n';
 import { hasGlobalPermission } from '../../helpers/users';
index 06b96e5814d222fa4808e084187860be489cbf11..c7cb7fcc3fb3d425e0db1f1080db19c19f9c10a9 100644 (file)
@@ -21,8 +21,8 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { WithRouterProps } from 'react-router';
 import { fetchQualityGates } from '../../../api/quality-gates';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import '../../../components/search-navigator.css';
 import DeferredSpinner from '../../../components/ui/DeferredSpinner';
 import { translate } from '../../../helpers/l10n';
index a5f66e43e181597d326b105d4c3e15d8e0c6d38a..7be27e7b99c325344147c5fda0559e423465edff 100644 (file)
@@ -20,8 +20,8 @@
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { Actions, getExporters, searchQualityProfiles } from '../../../api/quality-profiles';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import withLanguagesContext from '../../../app/components/languages/withLanguagesContext';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { Languages } from '../../../types/languages';
 import '../styles.css';
index efdbc427ba0aff8cd6f6804decf01269f5d3f8b8..f09495eb815553d778d96640f3d8175c5cf71cf1 100644 (file)
@@ -19,9 +19,9 @@
  */
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
 import ScreenPositionHelper from '../../components/common/ScreenPositionHelper';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import DeferredSpinner from '../../components/ui/DeferredSpinner';
 import { isBranch } from '../../helpers/branch-like';
 import { translate } from '../../helpers/l10n';
index 9008a85bf3722b3390786e5a6a59aca165fff3ab..4d5a07307e0ca249e1152f2fcb3f036cc7f3c108 100644 (file)
@@ -20,8 +20,8 @@
 import { uniqBy } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, withRouter } from '../../../components/hoc/withRouter';
 import { translate } from '../../../helpers/l10n';
 import { ExtendedSettingDefinition } from '../../../types/settings';
index f4a319102d47999c9d8367757e3147a177af9833..9beb5cee6c64b7bd5392b782b0ed6779dda9d251 100644 (file)
@@ -21,8 +21,8 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { withRouter, WithRouterProps } from 'react-router';
 import { getSystemInfo } from '../../../api/system';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
 import UpdateNotification from '../../../app/components/update-notification/UpdateNotification';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { SysInfoCluster, SysInfoStandalone } from '../../../types/types';
 import '../styles.css';
index 5ddcdda1afe4ded84615a8d4eea31dead7aac4cb..e788902f316ea1f88b0ef808999cf351605d356c 100644 (file)
@@ -21,8 +21,8 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { getIdentityProviders, searchUsers } from '../../api/users';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
-import Suggestions from '../../app/components/embed-docs-modal/Suggestions';
 import ListFooter from '../../components/controls/ListFooter';
+import Suggestions from '../../components/embed-docs-modal/Suggestions';
 import { Location, Router, withRouter } from '../../components/hoc/withRouter';
 import { translate } from '../../helpers/l10n';
 import { IdentityProvider, Paging } from '../../types/types';
index 6ddb808e86db1320ec657e1c7a58d3b4cdc09d82..c295e6438c2e7be9a85d69371dd6f21e1fee5652 100644 (file)
@@ -22,9 +22,9 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { Link, withRouter, WithRouterProps } from 'react-router';
 import { fetchWebApi } from '../../../api/web-api';
-import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
 import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages';
 import { scrollToElement } from '../../../helpers/scrolling';
index 7e057bf0cbc2918bbd97d3c46379411d75d79ef1..d2875d668247478e656ab1e3a894440430eb062e 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { createWebhook, deleteWebhook, searchWebhooks, updateWebhook } from '../../../api/webhooks';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { LightComponent, Webhook } from '../../../types/types';
 import PageActions from './PageActions';
diff --git a/server/sonar-web/src/main/js/components/a11y/A11yContext.tsx b/server/sonar-web/src/main/js/components/a11y/A11yContext.tsx
new file mode 100644 (file)
index 0000000..3bf2444
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { createContext } from 'react';
+import { A11ySkipLink } from '../../types/types';
+
+export interface A11yContextShape {
+  addA11ySkipLink: (link: A11ySkipLink) => void;
+  removeA11ySkipLink: (link: A11ySkipLink) => void;
+  links: A11ySkipLink[];
+}
+
+export const A11yContext = createContext<A11yContextShape>({
+  addA11ySkipLink: () => {
+    /* Implemented by Provider */
+  },
+  removeA11ySkipLink: () => {
+    /* Implemented by Provider */
+  },
+  links: []
+});
diff --git a/server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx b/server/sonar-web/src/main/js/components/a11y/A11yProvider.tsx
new file mode 100644 (file)
index 0000000..f515d9f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { sortBy } from 'lodash';
+import * as React from 'react';
+import { A11ySkipLink } from '../../types/types';
+import { A11yContext } from './A11yContext';
+
+interface State {
+  links: A11ySkipLink[];
+}
+
+export default class A11yProvider extends React.Component<{}, State> {
+  keys: string[] = [];
+  state: State = { links: [] };
+
+  addA11ySkipLink = (link: A11ySkipLink) => {
+    this.setState(prevState => {
+      const links = [...prevState.links];
+      links.push({ ...link, weight: link.weight || 0 });
+      return { links };
+    });
+  };
+
+  removeA11ySkipLink = (link: A11ySkipLink) => {
+    this.setState(prevState => {
+      const links = prevState.links.filter(l => l.key !== link.key);
+      return { links };
+    });
+  };
+
+  render() {
+    const links = sortBy(this.state.links, 'weight');
+    return (
+      <A11yContext.Provider
+        value={{
+          addA11ySkipLink: this.addA11ySkipLink,
+          links,
+          removeA11ySkipLink: this.removeA11ySkipLink
+        }}>
+        {this.props.children}
+      </A11yContext.Provider>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css b/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.css
new file mode 100644 (file)
index 0000000..e1eabb6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.
+ */
+.a11y-skip-link {
+  width: 0;
+  height: 0;
+  padding: var(--gridSize);
+  position: absolute;
+  left: -9999px;
+  top: -9999px;
+  border: 0;
+  font-size: 1rem;
+  text-align: center;
+  z-index: 999;
+}
+
+.a11y-skip-link:focus {
+  width: auto;
+  height: auto;
+  left: 6px;
+  top: 6px;
+  color: white;
+  background-color: var(--globalNavBarBg);
+  text-decoration: underline;
+}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx b/server/sonar-web/src/main/js/components/a11y/A11ySkipLinks.tsx
new file mode 100644 (file)
index 0000000..8af879b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { A11yContext } from './A11yContext';
+import './A11ySkipLinks.css';
+
+export default function A11ySkipLinks() {
+  return (
+    <A11yContext.Consumer>
+      {({ links }) =>
+        links.map(link => (
+          <a className="a11y-skip-link" href={`#a11y_target__${link.key}`} key={link.key}>
+            {link.label}
+          </a>
+        ))
+      }
+    </A11yContext.Consumer>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx b/server/sonar-web/src/main/js/components/a11y/A11ySkipTarget.tsx
new file mode 100644 (file)
index 0000000..59f56c9
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { translate } from '../../helpers/l10n';
+import { A11ySkipLink } from '../../types/types';
+import { A11yContext } from './A11yContext';
+
+interface Props {
+  anchor: string;
+  label?: string;
+  weight?: number;
+}
+
+export default function A11ySkipTarget(props: Props) {
+  return (
+    <A11yContext.Consumer>
+      {({ addA11ySkipLink, removeA11ySkipLink }) => (
+        <A11ySkipTargetInner
+          addA11ySkipLink={addA11ySkipLink}
+          removeA11ySkipLink={removeA11ySkipLink}
+          {...props}
+        />
+      )}
+    </A11yContext.Consumer>
+  );
+}
+
+interface InnerProps {
+  addA11ySkipLink: (link: A11ySkipLink) => void;
+  removeA11ySkipLink: (link: A11ySkipLink) => void;
+}
+
+export class A11ySkipTargetInner extends React.PureComponent<Props & InnerProps> {
+  componentDidMount() {
+    this.props.addA11ySkipLink(this.getLink());
+  }
+
+  componentWillUnmount() {
+    this.props.removeA11ySkipLink(this.getLink());
+  }
+
+  getLink = (): A11ySkipLink => {
+    const { anchor: key, label = translate('skip_to_content'), weight } = this.props;
+    return { key, label, weight };
+  };
+
+  render() {
+    const { anchor } = this.props;
+    return <span id={`a11y_target__${anchor}`} />;
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/A11yProvider-test.tsx b/server/sonar-web/src/main/js/components/a11y/__tests__/A11yProvider-test.tsx
new file mode 100644 (file)
index 0000000..5f4041d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from '../../../helpers/testUtils';
+import { A11yContextShape } from '../A11yContext';
+import A11yProvider from '../A11yProvider';
+
+const link1 = { key: 'link1', label: 'Link 1', weight: 0 };
+const link2 = { key: 'link2', label: 'Link 2', weight: -10 };
+const link3 = { key: 'link3', label: 'Link 3', weight: 0 };
+
+it('should allow to register new skip links', () => {
+  const wrapper = shallowRender();
+  const instance = wrapper.instance();
+  expect(wrapper.state('links')).toEqual([]);
+
+  // Check that an absence of weight is treated as "0".
+  instance.addA11ySkipLink({ ...link1, weight: undefined });
+  expect(wrapper.state('links')).toEqual([link1]);
+
+  instance.addA11ySkipLink(link2);
+  expect(wrapper.state('links')).toEqual([link1, link2]);
+});
+
+it('should pass the ordered links to the consumers', () => {
+  const wrapper = shallowRender();
+  const instance = wrapper.instance();
+  instance.setState({ links: [link1, link2, link3] });
+  waitAndUpdate(wrapper);
+  expect((wrapper.prop('value') as A11yContextShape).links).toEqual([link2, link1, link3]);
+});
+
+it('should allow to unregister skip links', () => {
+  const wrapper = shallowRender();
+  const instance = wrapper.instance();
+  instance.setState({ links: [link1, link2, link3] });
+
+  instance.removeA11ySkipLink(link1);
+  expect(wrapper.state('links')).toEqual([link2, link3]);
+
+  instance.removeA11ySkipLink(link2);
+  expect(wrapper.state('links')).toEqual([link3]);
+});
+
+function shallowRender() {
+  return shallow<A11yProvider>(
+    <A11yProvider>
+      <div />
+    </A11yProvider>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipLinks-test.tsx b/server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipLinks-test.tsx
new file mode 100644 (file)
index 0000000..f19805d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import A11ySkipLinks from '../A11ySkipLinks';
+
+jest.mock('../A11yContext', () => ({
+  A11yContext: {
+    Consumer: ({ children }: any) => {
+      return children({
+        links: [
+          { key: 'link1', label: 'Label 1' },
+          { key: 'link2', label: 'Label 2' }
+        ]
+      });
+    }
+  }
+}));
+
+it('should render correctly', () => {
+  expect(shallow(<A11ySkipLinks />).dive()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipTarget-test.tsx b/server/sonar-web/src/main/js/components/a11y/__tests__/A11ySkipTarget-test.tsx
new file mode 100644 (file)
index 0000000..55083e6
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { mount } from 'enzyme';
+import * as React from 'react';
+import { A11ySkipTargetInner } from '../A11ySkipTarget';
+
+it('should render correctly, and (un)register the link when (un)mounted', () => {
+  const link = { key: 'main', label: 'Skip to content' };
+  const addA11ySkipLink = jest.fn();
+  const removeA11ySkipLink = jest.fn();
+  const wrapper = mount(
+    <A11ySkipTargetInner
+      addA11ySkipLink={addA11ySkipLink}
+      anchor={link.key}
+      label={link.label}
+      removeA11ySkipLink={removeA11ySkipLink}
+    />
+  );
+
+  expect(wrapper).toMatchSnapshot();
+  expect(addA11ySkipLink).toBeCalledWith(link);
+  wrapper.unmount();
+  expect(removeA11ySkipLink).toBeCalledWith(link);
+});
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap b/server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap
new file mode 100644 (file)
index 0000000..a8a148a
--- /dev/null
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+Array [
+  <a
+    className="a11y-skip-link"
+    href="#a11y_target__link1"
+    key="link1"
+  >
+    Label 1
+  </a>,
+  <a
+    className="a11y-skip-link"
+    href="#a11y_target__link2"
+    key="link2"
+  >
+    Label 2
+  </a>,
+]
+`;
diff --git a/server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap b/server/sonar-web/src/main/js/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap
new file mode 100644 (file)
index 0000000..8fa4730
--- /dev/null
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly, and (un)register the link when (un)mounted 1`] = `
+<A11ySkipTargetInner
+  addA11ySkipLink={
+    [MockFunction] {
+      "calls": Array [
+        Array [
+          Object {
+            "key": "main",
+            "label": "Skip to content",
+            "weight": undefined,
+          },
+        ],
+      ],
+      "results": Array [
+        Object {
+          "type": "return",
+          "value": undefined,
+        },
+      ],
+    }
+  }
+  anchor="main"
+  label="Skip to content"
+  removeA11ySkipLink={[MockFunction]}
+>
+  <span
+    id="a11y_target__main"
+  />
+</A11ySkipTargetInner>
+`;
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx
new file mode 100644 (file)
index 0000000..0f8d1db
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { Link } from 'react-router';
+import { translate } from '../../helpers/l10n';
+import { getBaseUrl } from '../../helpers/system';
+import { SuggestionLink } from '../../types/types';
+import { DropdownOverlay } from '../controls/Dropdown';
+import { SuggestionsContext } from './SuggestionsContext';
+
+interface Props {
+  onClose: () => void;
+}
+
+export default class EmbedDocsPopup extends React.PureComponent<Props> {
+  renderTitle(text: string) {
+    return <li className="menu-header">{text}</li>;
+  }
+
+  renderSuggestions = ({ suggestions }: { suggestions: SuggestionLink[] }) => {
+    if (suggestions.length === 0) {
+      return null;
+    }
+    return (
+      <>
+        {this.renderTitle(translate('embed_docs.suggestion'))}
+        {suggestions.map((suggestion, index) => (
+          <li key={index}>
+            <Link onClick={this.props.onClose} target="_blank" to={suggestion.link}>
+              {suggestion.text}
+            </Link>
+          </li>
+        ))}
+        <li className="divider" />
+      </>
+    );
+  };
+
+  renderIconLink(link: string, icon: string, text: string) {
+    return (
+      <a href={link} rel="noopener noreferrer" target="_blank">
+        <img
+          alt={text}
+          className="spacer-right"
+          height="18"
+          src={`${getBaseUrl()}/images/${icon}`}
+          width="18"
+        />
+        {text}
+      </a>
+    );
+  }
+
+  render() {
+    return (
+      <DropdownOverlay>
+        <ul className="menu abs-width-240">
+          <SuggestionsContext.Consumer>{this.renderSuggestions}</SuggestionsContext.Consumer>
+          <li>
+            <Link onClick={this.props.onClose} target="_blank" to="/documentation">
+              {translate('embed_docs.documentation')}
+            </Link>
+          </li>
+          <li>
+            <Link onClick={this.props.onClose} to="/web_api">
+              {translate('api_documentation.page')}
+            </Link>
+          </li>
+          <li className="divider" />
+          <li>
+            <a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank">
+              {translate('embed_docs.get_help')}
+            </a>
+          </li>
+          <li className="divider" />
+          {this.renderTitle(translate('embed_docs.stay_connected'))}
+          <li>
+            {this.renderIconLink(
+              'https://www.sonarqube.org/whats-new/?referrer=sonarqube',
+              'embed-doc/sq-icon.svg',
+              translate('embed_docs.news')
+            )}
+          </li>
+          <li>
+            {this.renderIconLink(
+              'https://twitter.com/SonarQube',
+              'embed-doc/twitter-icon.svg',
+              'Twitter'
+            )}
+          </li>
+        </ul>
+      </DropdownOverlay>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
new file mode 100644 (file)
index 0000000..f34140b
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { translate } from '../../helpers/l10n';
+import { ButtonLink } from '../controls/buttons';
+import Toggler from '../controls/Toggler';
+import HelpIcon from '../icons/HelpIcon';
+import { lazyLoadComponent } from '../lazyLoadComponent';
+
+const EmbedDocsPopup = lazyLoadComponent(() => import('./EmbedDocsPopup'));
+
+interface State {
+  helpOpen: boolean;
+}
+
+export default class EmbedDocsPopupHelper extends React.PureComponent<{}, State> {
+  mounted = false;
+  state: State = { helpOpen: false };
+
+  setHelpDisplay = (helpOpen: boolean) => {
+    this.setState({ helpOpen });
+  };
+
+  handleClick = () => {
+    this.toggleHelp();
+  };
+
+  toggleHelp = () => {
+    this.setState(state => {
+      return { helpOpen: !state.helpOpen };
+    });
+  };
+
+  closeHelp = () => {
+    this.setState({ helpOpen: false });
+  };
+
+  render() {
+    return (
+      <li className="dropdown">
+        <Toggler
+          onRequestClose={this.closeHelp}
+          open={this.state.helpOpen}
+          overlay={<EmbedDocsPopup onClose={this.closeHelp} />}>
+          <ButtonLink
+            aria-expanded={this.state.helpOpen}
+            aria-haspopup={true}
+            className="navbar-help navbar-icon"
+            onClick={this.handleClick}
+            title={translate('help')}>
+            <HelpIcon />
+          </ButtonLink>
+        </Toggler>
+      </li>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/Suggestions.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/Suggestions.tsx
new file mode 100644 (file)
index 0000000..9cf36ba
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { SuggestionsContext } from './SuggestionsContext';
+
+interface Props {
+  suggestions: string;
+}
+
+export default function Suggestions({ suggestions }: Props) {
+  return (
+    <SuggestionsContext.Consumer>
+      {({ addSuggestions, removeSuggestions }) => (
+        <SuggestionsInner
+          addSuggestions={addSuggestions}
+          removeSuggestions={removeSuggestions}
+          suggestions={suggestions}
+        />
+      )}
+    </SuggestionsContext.Consumer>
+  );
+}
+
+interface SuggestionsInnerProps {
+  addSuggestions: (key: string) => void;
+  removeSuggestions: (key: string) => void;
+  suggestions: string;
+}
+
+class SuggestionsInner extends React.PureComponent<SuggestionsInnerProps> {
+  componentDidMount() {
+    this.props.addSuggestions(this.props.suggestions);
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.suggestions !== this.props.suggestions) {
+      this.props.removeSuggestions(this.props.suggestions);
+      this.props.addSuggestions(prevProps.suggestions);
+    }
+  }
+
+  componentWillUnmount() {
+    this.props.removeSuggestions(this.props.suggestions);
+  }
+
+  render() {
+    return null;
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsContext.ts b/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsContext.ts
new file mode 100644 (file)
index 0000000..e389713
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { createContext } from 'react';
+import { SuggestionLink } from '../../types/types';
+
+interface SuggestionsContextShape {
+  addSuggestions: (key: string) => void;
+  removeSuggestions: (key: string) => void;
+  suggestions: SuggestionLink[];
+}
+
+export const SuggestionsContext = createContext<SuggestionsContextShape>({
+  addSuggestions: () => {
+    /* Implemented by Provider */
+  },
+  removeSuggestions: () => {
+    /* Implemented by Provider */
+  },
+  suggestions: []
+});
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/SuggestionsProvider.tsx
new file mode 100644 (file)
index 0000000..89481d2
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
+import * as React from 'react';
+import { Dict, SuggestionLink } from '../../types/types';
+import { SuggestionsContext } from './SuggestionsContext';
+
+type SuggestionsJson = Dict<SuggestionLink[]>;
+
+interface State {
+  suggestions: SuggestionLink[];
+}
+
+export default class SuggestionsProvider extends React.Component<{}, State> {
+  keys: string[] = [];
+  state: State = { suggestions: [] };
+
+  fetchSuggestions = () => {
+    const jsonList = suggestionsJson as SuggestionsJson;
+    let suggestions: SuggestionLink[] = [];
+    this.keys.forEach(key => {
+      if (jsonList[key]) {
+        suggestions = [...jsonList[key], ...suggestions];
+      }
+    });
+
+    suggestions = suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
+
+    this.setState({ suggestions });
+  };
+
+  addSuggestions = (newKey: string) => {
+    this.keys = [...this.keys, newKey];
+    this.fetchSuggestions();
+  };
+
+  removeSuggestions = (oldKey: string) => {
+    this.keys = this.keys.filter(key => key !== oldKey);
+    this.fetchSuggestions();
+  };
+
+  render() {
+    return (
+      <SuggestionsContext.Provider
+        value={{
+          addSuggestions: this.addSuggestions,
+          removeSuggestions: this.removeSuggestions,
+          suggestions: this.state.suggestions
+        }}>
+        {this.props.children}
+      </SuggestionsContext.Provider>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
new file mode 100644 (file)
index 0000000..f12348e
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import EmbedDocsPopup from '../EmbedDocsPopup';
+
+it('should render', () => {
+  const wrapper = shallow(<EmbedDocsPopup onClose={jest.fn()} />);
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/SuggestionsProvider-test.tsx
new file mode 100644 (file)
index 0000000..4cff540
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import SuggestionsProvider from '../SuggestionsProvider';
+
+jest.mock(
+  'Docs/EmbedDocsSuggestions.json',
+  () => ({
+    pageA: [
+      { link: '/foo', text: 'Foo' },
+      { link: '/bar', text: 'Bar', scope: 'sonarcloud' }
+    ],
+    pageB: [{ link: '/qux', text: 'Qux' }]
+  }),
+  { virtual: true }
+);
+
+it('should add & remove suggestions', () => {
+  const wrapper = shallow<SuggestionsProvider>(
+    <SuggestionsProvider>
+      <div />
+    </SuggestionsProvider>
+  );
+  const instance = wrapper.instance();
+  expect(wrapper.state('suggestions')).toEqual([]);
+
+  instance.addSuggestions('pageA');
+  expect(wrapper.state('suggestions')).toEqual([{ link: '/foo', text: 'Foo' }]);
+
+  instance.addSuggestions('pageB');
+  expect(wrapper.state('suggestions')).toEqual([
+    { link: '/qux', text: 'Qux' },
+    { link: '/foo', text: 'Foo' }
+  ]);
+
+  instance.removeSuggestions('pageA');
+  expect(wrapper.state('suggestions')).toEqual([{ link: '/qux', text: 'Qux' }]);
+});
diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
new file mode 100644 (file)
index 0000000..29a379a
--- /dev/null
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<DropdownOverlay>
+  <ul
+    className="menu abs-width-240"
+  >
+    <ContextConsumer>
+      <Component />
+    </ContextConsumer>
+    <li>
+      <Link
+        onClick={[MockFunction]}
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        target="_blank"
+        to="/documentation"
+      >
+        embed_docs.documentation
+      </Link>
+    </li>
+    <li>
+      <Link
+        onClick={[MockFunction]}
+        onlyActiveOnIndex={false}
+        style={Object {}}
+        to="/web_api"
+      >
+        api_documentation.page
+      </Link>
+    </li>
+    <li
+      className="divider"
+    />
+    <li>
+      <a
+        href="https://community.sonarsource.com/"
+        rel="noopener noreferrer"
+        target="_blank"
+      >
+        embed_docs.get_help
+      </a>
+    </li>
+    <li
+      className="divider"
+    />
+    <li
+      className="menu-header"
+    >
+      embed_docs.stay_connected
+    </li>
+    <li>
+      <a
+        href="https://www.sonarqube.org/whats-new/?referrer=sonarqube"
+        rel="noopener noreferrer"
+        target="_blank"
+      >
+        <img
+          alt="embed_docs.news"
+          className="spacer-right"
+          height="18"
+          src="/images/embed-doc/sq-icon.svg"
+          width="18"
+        />
+        embed_docs.news
+      </a>
+    </li>
+    <li>
+      <a
+        href="https://twitter.com/SonarQube"
+        rel="noopener noreferrer"
+        target="_blank"
+      >
+        <img
+          alt="Twitter"
+          className="spacer-right"
+          height="18"
+          src="/images/embed-doc/twitter-icon.svg"
+          width="18"
+        />
+        Twitter
+      </a>
+    </li>
+  </ul>
+</DropdownOverlay>
+`;