* 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';
* 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;
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;
+++ /dev/null
-/*
- * 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: []
-});
+++ /dev/null
-/*
- * 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>
- );
- }
-}
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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>
- );
-}
+++ /dev/null
-/*
- * 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}`} />;
- }
-}
+++ /dev/null
-/*
- * 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>
- );
-}
+++ /dev/null
-/*
- * 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();
-});
+++ /dev/null
-/*
- * 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);
-});
+++ /dev/null
-// 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>,
-]
-`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-/*
- * 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>
- );
- }
-}
+++ /dev/null
-/*
- * 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>
- );
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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: []
-});
+++ /dev/null
-/*
- * 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>
- );
- }
-}
+++ /dev/null
-/*
- * 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();
-});
+++ /dev/null
-/*
- * 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' }
- ]);
-});
+++ /dev/null
-// 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>
-`;
*/
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';
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';
getMeasureHistoryUrl,
getRulesUrl
} from '../../../helpers/urls';
-import A11ySkipTarget from '../a11y/A11ySkipTarget';
-import Suggestions from '../embed-docs-modal/Suggestions';
const exposeLibraries = () => {
const global = window as any;
* 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';
*/
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';
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';
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';
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';
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';
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';
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';
*/
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';
* 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';
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';
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';
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';
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';
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';
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';
* 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';
*/
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';
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';
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';
*/
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';
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';
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';
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';
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';
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';
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';
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';
*/
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';
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';
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';
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';
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';
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';
--- /dev/null
+/*
+ * 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: []
+});
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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}`} />;
+ }
+}
--- /dev/null
+/*
+ * 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>
+ );
+}
--- /dev/null
+/*
+ * 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();
+});
--- /dev/null
+/*
+ * 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);
+});
--- /dev/null
+// 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>,
+]
+`;
--- /dev/null
+// 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>
+`;
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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: []
+});
--- /dev/null
+/*
+ * 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>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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();
+});
--- /dev/null
+/*
+ * 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' }]);
+});
--- /dev/null
+// 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>
+`;