]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18503 Read documentation base URL from backend to generate documentation link
authorstanislavh <stanislav.honcharov@sonarsource.com>
Thu, 4 May 2023 08:26:09 +0000 (10:26 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 5 May 2023 20:02:59 +0000 (20:02 +0000)
22 files changed:
server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx
server/sonar-web/src/main/js/app/components/__tests__/DocumentationRedirect-test.tsx
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap
server/sonar-web/src/main/js/app/components/app-state/AppStateContext.tsx
server/sonar-web/src/main/js/app/components/app-state/__tests__/__snapshots__/AppStateContextProvider-test.tsx.snap
server/sonar-web/src/main/js/app/components/extensions/__tests__/__snapshots__/Extension-test.tsx.snap
server/sonar-web/src/main/js/app/components/indexation/__tests__/__snapshots__/IndexationContextProvider-test.tsx.snap
server/sonar-web/src/main/js/apps/maintenance/components/App.tsx
server/sonar-web/src/main/js/apps/marketplace/__tests__/__snapshots__/App-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/branches/ApplicationNonCaycProjectWarning.tsx
server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarning.tsx
server/sonar-web/src/main/js/apps/overview/branches/CleanAsYouCodeWarningOverCompliant.tsx
server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/PageHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/users/components/__tests__/DeactivateForm-test.tsx
server/sonar-web/src/main/js/apps/webhooks/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
server/sonar-web/src/main/js/components/common/DocLink.tsx
server/sonar-web/src/main/js/components/embed-docs-modal/DocItemLink.tsx
server/sonar-web/src/main/js/components/upgrade/__tests__/__snapshots__/SystemUpgradeItem-test.tsx.snap
server/sonar-web/src/main/js/helpers/docs.ts
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/helpers/testReactTestingUtils.tsx
server/sonar-web/src/main/js/types/appstate.ts

index 88a0eaa06c73029de8bc404f7df3975f294f5c26..dd9a2cc148ec7e202d366f284ae078055d98caac 100644 (file)
@@ -21,14 +21,13 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { useLocation } from 'react-router-dom';
 import Link from '../../components/common/Link';
-import { getUrlForDoc } from '../../helpers/docs';
-import withAppStateContext, { WithAppStateContextProps } from './app-state/withAppStateContext';
+import { useDocUrl } from '../../helpers/docs';
 
 const PAUSE_REDIRECT = 1;
 
-function DocumentationRedirect({ appState }: WithAppStateContextProps) {
+export default function DocumentationRedirect() {
   const location = useLocation();
-  const url = getUrlForDoc(appState.version, location.pathname.replace(/^\/documentation/, ''));
+  const url = useDocUrl(location.pathname.replace(/^\/documentation/, ''));
 
   return (
     <>
@@ -47,5 +46,3 @@ function DocumentationRedirect({ appState }: WithAppStateContextProps) {
     </>
   );
 }
-
-export default withAppStateContext(DocumentationRedirect);
index 49e54de4d34591aaa980e2fb5fc902ad454974b7..a8db8911181476611942327128ef9c1ee6450633 100644 (file)
@@ -25,16 +25,16 @@ import { renderAppRoutes } from '../../../helpers/testReactTestingUtils';
 import DocumentationRedirect from '../DocumentationRedirect';
 
 it('should redirect to static doc for specific version', async () => {
-  renderDocumentationRedirect('land', '9.7.1234');
+  renderDocumentationRedirect('land', '10.0');
 
   expect(await screen.findByRole('link')).toHaveAttribute(
     'href',
-    'https://docs.sonarqube.org/9.7/land'
+    'https://docs.sonarqube.org/10.0/land'
   );
 });
 
 it('should redirect to static doc for latest version', async () => {
-  renderDocumentationRedirect('land', '9.7-SNAPSHOT');
+  renderDocumentationRedirect('land', '10.0-SNAPSHOT');
 
   expect(await screen.findByRole('link')).toHaveAttribute(
     'href',
@@ -42,9 +42,9 @@ it('should redirect to static doc for latest version', async () => {
   );
 });
 
-function renderDocumentationRedirect(navigatge: string, version?: string) {
+function renderDocumentationRedirect(navigate: string, version?: string) {
   renderAppRoutes(
-    `documentation/${navigatge}`,
+    `documentation/${navigate}`,
     () => <Route path="/documentation/*" element={<DocumentationRedirect />} />,
     { appState: mockAppState({ version }) }
   );
index 492e71658d25aece7b3be0938919ca8303865f53..6769c24e32a24d28f4b0efa3e6debec90b1a8d30 100644 (file)
@@ -44,20 +44,20 @@ exports[`should display the sq version 1`] = `
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/"
       >
         footer.documentation
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/instance-administration/plugin-version-matrix/"
       >
         footer.plugins
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
     <li
       className="page-footer-menu-item"
@@ -106,20 +106,20 @@ exports[`should not render the only logged in information 1`] = `
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/"
       >
         footer.documentation
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/instance-administration/plugin-version-matrix/"
       >
         footer.plugins
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
   </ul>
 </div>
@@ -169,20 +169,20 @@ exports[`should render the only logged in information 1`] = `
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/"
       >
         footer.documentation
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
     <li
       className="page-footer-menu-item"
     >
-      <withAppStateContext(DocLink)
+      <DocLink
         to="/instance-administration/plugin-version-matrix/"
       >
         footer.plugins
-      </withAppStateContext(DocLink)>
+      </DocLink>
     </li>
     <li
       className="page-footer-menu-item"
index ec47c99a40adcebf7d823d953adf0d177e8146d1..ed03861f3e59f025dc08d45f5e32d2820e7c6e6c 100644 (file)
@@ -28,5 +28,6 @@ export const DEFAULT_APP_STATE = {
   qualifiers: [],
   settings: {},
   version: '',
+  documentationUrl: 'https://docs.sonarqube.org/latest',
 };
 export const AppStateContext = React.createContext<AppState>(DEFAULT_APP_STATE);
index dbdc02fa1c41a30124c4d463cd8dd70443da4207..9881aed06e7f346f69c361f393d81aee57bba9c9 100644 (file)
@@ -4,6 +4,7 @@ exports[`should set value correctly 1`] = `
 <AppStateContextProvider
   appState={
     {
+      "documentationUrl": "https://docs.sonarqube.org/10.0",
       "edition": "community",
       "productionDatabase": true,
       "qualifiers": [
index 2aece96de2387b16e819c9836f232ef3b17ce6ae..2951d51017d17c7ade836906387b6d70e4b93ca5 100644 (file)
@@ -4,6 +4,7 @@ exports[`should render React extensions correctly 1`] = `
 <Extension
   appState={
     {
+      "documentationUrl": "https://docs.sonarqube.org/10.0",
       "edition": "community",
       "productionDatabase": true,
       "qualifiers": [
@@ -66,6 +67,7 @@ exports[`should render React extensions correctly 2`] = `
 <Extension
   appState={
     {
+      "documentationUrl": "https://docs.sonarqube.org/10.0",
       "edition": "community",
       "productionDatabase": true,
       "qualifiers": [
index b2fda45425a8f7fd50cb9cf0c9320b2d2747e4a2..c8202de2b5438bb1ab64892dcde1ff2f5b7d0d40 100644 (file)
@@ -4,6 +4,7 @@ exports[`should render correctly and start polling if issue sync is needed 1`] =
 <IndexationContextProvider
   appState={
     {
+      "documentationUrl": "https://docs.sonarqube.org/10.0",
       "edition": "community",
       "needIssueSync": true,
       "productionDatabase": true,
index c19d1712d632543411d55d3416f7e39fb3bc0f3e..4d0b2532c4658398e3b1d51bd1d4d09b6be3a2a3 100644 (file)
@@ -22,6 +22,7 @@ import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { FormattedMessage } from 'react-intl';
 import { getMigrationStatus, getSystemStatus, migrateDatabase } from '../../../api/system';
+import DocLink from '../../../components/common/DocLink';
 import InstanceMessage from '../../../components/common/InstanceMessage';
 import Link from '../../../components/common/Link';
 import { Button } from '../../../components/controls/buttons';
@@ -227,15 +228,9 @@ export default class App extends React.PureComponent<Props, State> {
                     id="maintenance.sonarqube_is_under_maintenance.2"
                     values={{
                       link: (
-                        <Link
-                          // We cannot use <DocLink> here, as it relies on AppState. However, the maintenance
-                          // app is a special app that can run in a "downgraded" environment, where the AppState
-                          // may not yet be fully loaded. Hence, we link to this documentation page directly.
-                          to="https://docs.sonarqube.org/latest/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
-                          target="_blank"
-                        >
+                        <DocLink to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/">
                           {translate('maintenance.sonarqube_is_under_maintenance_link.2')}
-                        </Link>
+                        </DocLink>
                       ),
                     }}
                   />
index 423ae4b0dd4ac02ad2d55546515b847a1cb175cb..dddad9f865d36447301b1d5f651e74669c207f3d 100644 (file)
@@ -43,11 +43,11 @@ exports[`should render correctly: loaded 1`] = `
           id="marketplace.page.plugins.description2"
           values={
             {
-              "link": <withAppStateContext(DocLink)
+              "link": <DocLink
                 to="/instance-administration/marketplace/"
               >
                 marketplace.page.plugins.description2.link
-              </withAppStateContext(DocLink)>,
+              </DocLink>,
             }
           }
         />
@@ -140,11 +140,11 @@ exports[`should render correctly: loading 1`] = `
           id="marketplace.page.plugins.description2"
           values={
             {
-              "link": <withAppStateContext(DocLink)
+              "link": <DocLink
                 to="/instance-administration/marketplace/"
               >
                 marketplace.page.plugins.description2.link
-              </withAppStateContext(DocLink)>,
+              </DocLink>,
             }
           }
         />
index e8f6fc3c1bccd501255b35f43059922df0d7db28..0cbadd8fc016b818b27fc2f5d1f52a392491b960 100644 (file)
  */
 import { Card, FlagMessage, Link } from 'design-system';
 import * as React from 'react';
-import withAppStateContext, {
-  WithAppStateContextProps,
-} from '../../../app/components/app-state/withAppStateContext';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
-import { getUrlForDoc } from '../../../helpers/docs';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getProjectQueryUrl } from '../../../helpers/urls';
 import { QualityGateStatus } from '../../../types/quality-gates';
@@ -34,16 +31,9 @@ interface Props {
   caycStatus: CaycStatus;
 }
 
-function ApplicationNonCaycProjectWarning({
-  projects,
-  caycStatus,
-  appState,
-}: Props & WithAppStateContextProps) {
-  const caycUrl = getUrlForDoc(appState.version, '/user-guide/clean-as-you-code/');
-  const caycDrawbacksUrl = getUrlForDoc(
-    appState.version,
-    '/user-guide/clean-as-you-code/#potential-drawbacks'
-  );
+export default function ApplicationNonCaycProjectWarning({ projects, caycStatus }: Props) {
+  const caycUrl = useDocUrl('/user-guide/clean-as-you-code/');
+  const caycDrawbacksUrl = useDocUrl('/user-guide/clean-as-you-code/#potential-drawbacks');
 
   return (
     <Card className="sw-mt-4 sw-body-sm">
@@ -89,5 +79,3 @@ function ApplicationNonCaycProjectWarning({
     </Card>
   );
 }
-
-export default withAppStateContext(ApplicationNonCaycProjectWarning);
index 746f5fa5a7fa94b4e17d76d925264fe834c2ff71..d54f177c953a4effb53cf8bb928fb4d2e8e72bc2 100644 (file)
 import { DiscreetLink, FlagMessage, Link } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import withAppStateContext, {
-  WithAppStateContextProps,
-} from '../../../app/components/app-state/withAppStateContext';
-import { getUrlForDoc } from '../../../helpers/docs';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
 import { getQualityGateUrl } from '../../../helpers/urls';
 import { Component } from '../../../types/types';
@@ -32,8 +29,8 @@ interface Props {
   component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
 }
 
-function CleanAsYouCodeWarning({ component, appState }: Props & WithAppStateContextProps) {
-  const caycUrl = getUrlForDoc(appState.version, '/user-guide/clean-as-you-code/');
+export default function CleanAsYouCodeWarning({ component }: Props) {
+  const caycUrl = useDocUrl('/user-guide/clean-as-you-code/');
 
   return (
     <>
@@ -65,5 +62,3 @@ function CleanAsYouCodeWarning({ component, appState }: Props & WithAppStateCont
     </>
   );
 }
-
-export default withAppStateContext(CleanAsYouCodeWarning);
index c2a5e08982ef18fda031601f280ded3d78edab36..38a2b966f99a90e63489a2bbefb949cd4eb02772 100644 (file)
 import { DiscreetLink, Link } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import withAppStateContext, {
-  WithAppStateContextProps,
-} from '../../../app/components/app-state/withAppStateContext';
-import { getUrlForDoc } from '../../../helpers/docs';
+import { useDocUrl } from '../../../helpers/docs';
 import { translate } from '../../../helpers/l10n';
 import { getQualityGateUrl } from '../../../helpers/urls';
 import { Component } from '../../../types/types';
@@ -32,14 +29,8 @@ interface Props {
   component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
 }
 
-function CleanAsYouCodeWarningOverCompliant({
-  component,
-  appState,
-}: Props & WithAppStateContextProps) {
-  const caycDrawbackUrl = getUrlForDoc(
-    appState.version,
-    '/user-guide/clean-as-you-code/#potential-drawbacks'
-  );
+export default function CleanAsYouCodeWarningOverCompliant({ component }: Props) {
+  const caycDrawbackUrl = useDocUrl('/user-guide/clean-as-you-code/#potential-drawbacks');
 
   return (
     <>
@@ -71,5 +62,3 @@ function CleanAsYouCodeWarningOverCompliant({
     </>
   );
 }
-
-export default withAppStateContext(CleanAsYouCodeWarningOverCompliant);
index b5ea540762ff178fc46be2ad9c34d26db0d1bd61..f0c9b17a8638525a416beff4b466726b3f29eacb 100644 (file)
@@ -15,12 +15,12 @@ exports[`should render correctly 1`] = `
     quality_profiles.intro1
     <br />
     quality_profiles.intro2
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/instance-administration/quality-profiles/"
     >
       learn_more
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </header>
 `;
@@ -58,12 +58,12 @@ exports[`should render correctly 2`] = `
     quality_profiles.intro1
     <br />
     quality_profiles.intro2
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/instance-administration/quality-profiles/"
     >
       learn_more
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </header>
 `;
@@ -107,12 +107,12 @@ exports[`should render correctly 3`] = `
     quality_profiles.intro1
     <br />
     quality_profiles.intro2
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/instance-administration/quality-profiles/"
     >
       learn_more
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </header>
 `;
@@ -150,12 +150,12 @@ exports[`should show a create form 1`] = `
     quality_profiles.intro1
     <br />
     quality_profiles.intro2
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/instance-administration/quality-profiles/"
     >
       learn_more
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
   <CreateProfileForm
     languages={
@@ -233,12 +233,12 @@ exports[`should show a restore form 1`] = `
     quality_profiles.intro1
     <br />
     quality_profiles.intro2
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/instance-administration/quality-profiles/"
     >
       learn_more
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
   <RestoreProfileForm
     onClose={[Function]}
index 0795cdddcfd2991d349c5e15937cec5a08f06a61..0a9f8e15941027fbc7237f6c3da822b66e3c1bbd 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { render, screen } from '@testing-library/react';
+import { screen } from '@testing-library/react';
 import * as React from 'react';
-import { IntlProvider } from 'react-intl';
 import { deactivateUser } from '../../../../api/users';
 import { mockUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { UserActive } from '../../../../types/users';
 import DeactivateForm from '../DeactivateForm';
 
@@ -41,9 +41,7 @@ it('should deactivate user with anonymize set to true', () => {
 });
 
 function renderDeactivateForm(user: UserActive) {
-  return render(
-    <IntlProvider defaultLocale="en" locale="en">
-      <DeactivateForm onClose={jest.fn()} onUpdateUsers={jest.fn()} user={user} />
-    </IntlProvider>
+  return renderComponent(
+    <DeactivateForm onClose={jest.fn()} onUpdateUsers={jest.fn()} user={user} />
   );
 }
index 14d8687ab1e236780e8ad66fad175a54932de3bf..ac418761feba209ce5525fd618c30622c77e07a3 100644 (file)
@@ -21,11 +21,11 @@ exports[`should render correctly 1`] = `
       id="webhooks.description"
       values={
         {
-          "url": <withAppStateContext(DocLink)
+          "url": <DocLink
             to="/project-administration/webhooks/"
           >
             webhooks.documentation_link
-          </withAppStateContext(DocLink)>,
+          </DocLink>,
         }
       }
     />
index 08c01031675836172954902442a934a4090ff3f6..6039b2d1b30b6c394ffd896bceb660048a4317ef 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import withAppStateContext, {
-  WithAppStateContextProps,
-} from '../../app/components/app-state/withAppStateContext';
-import { getUrlForDoc } from '../../helpers/docs';
+import { useDocUrl } from '../../helpers/docs';
 import Link, { LinkProps } from './Link';
 
-type Props = WithAppStateContextProps &
-  Omit<LinkProps, 'to'> & { to: string; innerRef?: React.Ref<HTMLAnchorElement> };
+type Props = Omit<LinkProps, 'to'> & { to: string; innerRef?: React.Ref<HTMLAnchorElement> };
 
-export function DocLink({ appState, to, innerRef, ...props }: Props) {
-  const toStatic = getUrlForDoc(appState.version, to);
+export default function DocLink({ to, innerRef, ...props }: Props) {
+  const toStatic = useDocUrl(to);
   return <Link ref={innerRef} to={toStatic} target="_blank" {...props} />;
 }
-
-export default withAppStateContext(DocLink);
index dd5939ff1d9fe15ab93b0954c8dcdf3af5a1f848..083c5856d293f23db1834265db479fa25b4f27c3 100644 (file)
@@ -20,9 +20,7 @@
 
 import { ItemLink, OpenNewTabIcon } from 'design-system';
 import * as React from 'react';
-import { AppStateContext } from '../../app/components/app-state/AppStateContext';
-
-import { getUrlForDoc } from '../../helpers/docs';
+import { useDocUrl } from '../../helpers/docs';
 
 interface Props {
   to: string;
@@ -31,9 +29,7 @@ interface Props {
 }
 
 export function DocItemLink({ to, innerRef, children }: Props) {
-  const { version } = React.useContext(AppStateContext);
-
-  const toStatic = getUrlForDoc(version, to);
+  const toStatic = useDocUrl(to);
 
   return (
     <ItemLink innerRef={innerRef} to={toStatic}>
index 2ab2d84ff4de8d530abc50e31afc56fb25f6c355..b9494c41c54ea0f5dab914f9223b9f786e8b9661 100644 (file)
@@ -89,12 +89,12 @@ exports[`should display correctly 1`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -188,12 +188,12 @@ exports[`should display correctly 2`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -280,12 +280,12 @@ exports[`should display correctly 3`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -379,12 +379,12 @@ exports[`should display correctly 4`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -478,12 +478,12 @@ exports[`should display correctly 5`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -577,12 +577,12 @@ exports[`should display correctly 6`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
@@ -657,12 +657,12 @@ exports[`should display correctly 7`] = `
     >
       system.download_x.5.6.7
     </a>
-    <withAppStateContext(DocLink)
+    <DocLink
       className="spacer-left"
       to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"
     >
       system.how_to_upgrade
-    </withAppStateContext(DocLink)>
+    </DocLink>
   </div>
 </div>
 `;
index 5bcb593339fd2d9a3020fdc10d72f5ca7bd7183d..1e106d55f241623aa39a24b243d528cfd0a4e58b 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-const VERSION_PARSER = /^(\d+\.\d+).*$/;
+import React from 'react';
+import { AppStateContext } from '../app/components/app-state/AppStateContext';
 
-export function getUrlForDoc(version: string, to: string) {
-  const versionPrefix = VERSION_PARSER.exec(version);
+export function getUrlForDoc(url: string, version: string, to: string) {
   const isSnapshot = version.indexOf('SNAPSHOT') !== -1;
-  const docPrefix =
-    versionPrefix && versionPrefix.length === 2 && !isSnapshot ? versionPrefix[1] : 'latest';
+  const path = to.replace(/^\//, '');
 
-  return `https://docs.sonarqube.org/${docPrefix}${to}`;
+  return isSnapshot
+    ? `${url.replace(url.slice(url.lastIndexOf('/')), '/latest')}/${path}`
+    : `${url}/${path}`;
+}
+
+export function useDocUrl(to: string) {
+  const { version, documentationUrl } = React.useContext(AppStateContext);
+
+  return getUrlForDoc(documentationUrl, version, to);
 }
index c5646eff516f5926f7fc3b1da39e5f4747b01b23..fe4dd38fa583fb4a656290bdc072cd0dc69ffccb 100644 (file)
@@ -74,6 +74,7 @@ export function mockAppState(overrides: Partial<AppState> = {}): AppState {
     qualifiers: ['TRK'],
     settings: {},
     version: '1.0',
+    documentationUrl: 'https://docs.sonarqube.org/10.0',
     ...overrides,
   };
 }
index 55e58f6a28ced09ba2c639a4421d95559c8bf6f4..53c99fe652ad8878d07c0d55b57e3e04e8462813 100644 (file)
@@ -90,16 +90,22 @@ export function renderAppWithAdminContext(
   );
 }
 
-export function renderComponent(component: React.ReactElement, pathname = '/') {
+export function renderComponent(
+  component: React.ReactElement,
+  pathname = '/',
+  { appState = mockAppState() }: RenderContext = {}
+) {
   function Wrapper({ children }: { children: React.ReactElement }) {
     return (
       <IntlProvider defaultLocale="en" locale="en">
         <HelmetProvider>
-          <MemoryRouter initialEntries={[pathname]}>
-            <Routes>
-              <Route path="*" element={children} />
-            </Routes>
-          </MemoryRouter>
+          <AppStateContextProvider appState={appState}>
+            <MemoryRouter initialEntries={[pathname]}>
+              <Routes>
+                <Route path="*" element={children} />
+              </Routes>
+            </MemoryRouter>
+          </AppStateContextProvider>
         </HelmetProvider>
       </IntlProvider>
     );
index 7e45a4e8e2afd88cfe95aeea182ba7697254a1f9..8b5e53b562e5150575ebe749ed2853caa911b54c 100644 (file)
@@ -35,4 +35,5 @@ export interface AppState {
   standalone?: boolean;
   version: string;
   webAnalyticsJsPath?: string;
+  documentationUrl: string;
 }