]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14496 Rely on sonar.core.serverBaseURL in CI tutorials
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Mon, 22 Feb 2021 14:46:12 +0000 (15:46 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 23 Feb 2021 20:07:26 +0000 (20:07 +0000)
18 files changed:
server/sonar-web/src/main/js/components/tutorials/TutorialSelection.tsx
server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-test.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelectionRenderer-test.tsx
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelection-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/__tests__/__snapshots__/TutorialSelectionRenderer-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/AzurePipelinesTutorial.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/ServiceEndpointStepContent.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/AzurePipelinesTutorial-test.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/ServiceEndpointStepContent-test.tsx
server/sonar-web/src/main/js/components/tutorials/azure-pipelines/__tests__/__snapshots__/ServiceEndpointStepContent-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/EnvironmentVariablesStep.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/EnvironmentVariablesStep-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/GitLabCITutorial-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/EnvironmentVariablesStep-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap
server/sonar-web/src/main/js/types/settings.ts

index 64a0eab297ea91b416947bddbc832611a94ef6d1..ddf797e83c22f182912a6aec034a68b8801a89d0 100644 (file)
  */
 import * as React from 'react';
 import { WithRouterProps } from 'react-router';
+import { getHostUrl } from 'sonar-ui-common/helpers/urls';
 import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../api/alm-settings';
+import { getValues } from '../../api/settings';
 import { AlmBindingDefinition, ProjectAlmBindingResponse } from '../../types/alm-settings';
+import { SettingsKey } from '../../types/settings';
 import { withRouter } from '../hoc/withRouter';
 import TutorialSelectionRenderer from './TutorialSelectionRenderer';
 import { TutorialModes } from './types';
@@ -32,6 +35,7 @@ interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
 
 interface State {
   almBinding?: AlmBindingDefinition;
+  baseUrl: string;
   forceManual: boolean;
   loading: boolean;
   projectBinding?: ProjectAlmBindingResponse;
@@ -40,13 +44,23 @@ interface State {
 export class TutorialSelection extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = {
+    baseUrl: getHostUrl(),
     forceManual: true,
     loading: true
   };
 
-  componentDidMount() {
+  async componentDidMount() {
     this.mounted = true;
-    this.fetchAlmBindings();
+
+    await Promise.all([this.fetchAlmBindings(), this.fetchBaseUrl()]);
+
+    if (this.mounted) {
+      this.setState({ loading: false });
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
   }
 
   fetchAlmBindings = async () => {
@@ -59,18 +73,26 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
 
     if (this.mounted) {
       if (projectBinding === undefined) {
-        this.setState({ loading: false, forceManual: true });
+        this.setState({ forceManual: true });
       } else {
         let almBinding;
         if (almDefinitions !== undefined) {
           const specificDefinitions = almDefinitions[projectBinding.alm] as AlmBindingDefinition[];
           almBinding = specificDefinitions.find(d => d.key === projectBinding.key);
         }
-        this.setState({ almBinding, forceManual: false, projectBinding, loading: false });
+        this.setState({ almBinding, forceManual: false, projectBinding });
       }
     }
   };
 
+  fetchBaseUrl = async () => {
+    const settings = await getValues({ keys: SettingsKey.ServerBaseUrl }).catch(() => undefined);
+    const baseUrl = settings && settings.find(s => s.key === SettingsKey.ServerBaseUrl)?.value;
+    if (baseUrl && baseUrl.length > 0 && this.mounted) {
+      this.setState({ baseUrl });
+    }
+  };
+
   handleSelectTutorial = (selectedTutorial: TutorialModes) => {
     const {
       router,
@@ -85,7 +107,7 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
 
   render() {
     const { component, currentUser, location } = this.props;
-    const { almBinding, forceManual, loading, projectBinding } = this.state;
+    const { almBinding, baseUrl, forceManual, loading, projectBinding } = this.state;
 
     const selectedTutorial: TutorialModes | undefined = forceManual
       ? TutorialModes.Manual
@@ -94,6 +116,7 @@ export class TutorialSelection extends React.PureComponent<Props, State> {
     return (
       <TutorialSelectionRenderer
         almBinding={almBinding}
+        baseUrl={baseUrl}
         component={component}
         currentUser={currentUser}
         loading={loading}
index 0689d78aff4f1c93169df80b8bf762799a995801..323c6a682cca9c0bf72b0a4ede3b1ff4fe380ed5 100644 (file)
@@ -29,6 +29,7 @@ import { TutorialModes } from './types';
 
 export interface TutorialSelectionRendererProps {
   almBinding?: AlmBindingDefinition;
+  baseUrl: string;
   component: T.Component;
   currentUser: T.LoggedInUser;
   loading: boolean;
@@ -38,7 +39,15 @@ export interface TutorialSelectionRendererProps {
 }
 
 export default function TutorialSelectionRenderer(props: TutorialSelectionRendererProps) {
-  const { almBinding, component, currentUser, loading, projectBinding, selectedTutorial } = props;
+  const {
+    almBinding,
+    baseUrl,
+    component,
+    currentUser,
+    loading,
+    projectBinding,
+    selectedTutorial
+  } = props;
 
   if (loading) {
     return <i className="spinner" />;
@@ -138,6 +147,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
 
       {selectedTutorial === TutorialModes.GitLabCI && projectBinding !== undefined && (
         <GitLabCITutorial
+          baseUrl={baseUrl}
           component={component}
           currentUser={currentUser}
           projectBinding={projectBinding}
@@ -146,6 +156,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
 
       {selectedTutorial === TutorialModes.AzurePipelines && projectBinding !== undefined && (
         <AzurePipelinesTutorial
+          baseUrl={baseUrl}
           component={component}
           currentUser={currentUser}
           projectBinding={projectBinding}
index 76b4dbe9d79e4f5d206ff53660c1bb583d80dacf..41bcdf0f729be28191b1d1973e9f33a67a6850aa 100644 (file)
@@ -20,7 +20,9 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getHostUrl } from 'sonar-ui-common/helpers/urls';
 import { getAlmDefinitionsNoCatch, getProjectAlmBinding } from '../../../api/alm-settings';
+import { getValues } from '../../../api/settings';
 import { mockBitbucketBindingDefinition } from '../../../helpers/mocks/alm-settings';
 import {
   mockComponent,
@@ -29,14 +31,23 @@ import {
   mockRouter
 } from '../../../helpers/testMocks';
 import { AlmKeys } from '../../../types/alm-settings';
+import { SettingsKey } from '../../../types/settings';
 import { TutorialSelection } from '../TutorialSelection';
 import { TutorialModes } from '../types';
 
+jest.mock('sonar-ui-common/helpers/urls', () => ({
+  getHostUrl: jest.fn().mockReturnValue('http://host.url')
+}));
+
 jest.mock('../../../api/alm-settings', () => ({
   getProjectAlmBinding: jest.fn().mockRejectedValue(null),
   getAlmDefinitionsNoCatch: jest.fn().mockRejectedValue(null)
 }));
 
+jest.mock('../../../api/settings', () => ({
+  getValues: jest.fn().mockResolvedValue([])
+}));
+
 beforeEach(jest.clearAllMocks);
 
 it('should render correctly', () => {
@@ -88,6 +99,32 @@ it('should handle selection', () => {
   );
 });
 
+it('should fetch the correct baseUrl', async () => {
+  (getValues as jest.Mock)
+    .mockResolvedValueOnce([{ key: SettingsKey.ServerBaseUrl, value: '' }])
+    .mockResolvedValueOnce([{ key: SettingsKey.ServerBaseUrl, value: 'http://sq.example.com' }])
+    .mockRejectedValueOnce(null);
+
+  let wrapper = shallowRender();
+
+  expect(getValues).toBeCalled();
+  expect(getHostUrl).toBeCalled();
+
+  // No baseURL, fallback to the URL in the browser.
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().baseUrl).toBe('http://host.url');
+
+  // A baseURL was set.
+  wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().baseUrl).toBe('http://sq.example.com');
+
+  // Access denied, fallback to the URL in the browser.
+  wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state().baseUrl).toBe('http://host.url');
+});
+
 function shallowRender(props: Partial<TutorialSelection['props']> = {}) {
   return shallow<TutorialSelection>(
     <TutorialSelection
index 7efd8c10f88317d856219c5a10572dd1f7037750..5ff0893230d6d6eba46099df64572e261bea60a6 100644 (file)
@@ -125,6 +125,7 @@ function shallowRender(props: Partial<TutorialSelectionRendererProps> = {}) {
   return shallow<TutorialSelectionRendererProps>(
     <TutorialSelectionRenderer
       almBinding={mockBitbucketBindingDefinition()}
+      baseUrl="http://localhost:9000"
       component={mockComponent()}
       currentUser={mockLoggedInUser()}
       loading={false}
index 4ec730fade58ba23712c43d38611ccba67222f53..f686da4184a671c7d81ce52338d17f856c8dd88b 100644 (file)
@@ -2,6 +2,7 @@
 
 exports[`should render correctly 1`] = `
 <TutorialSelectionRenderer
+  baseUrl="http://host.url"
   component={
     Object {
       "breadcrumbs": Array [],
index bdc9b40712c00292eca6be42f9c399c8019992f8..bcdfec2e0169a372eaf5b2c00b787ff671677da6 100644 (file)
@@ -3,6 +3,7 @@
 exports[`should render correctly: azure pipelines tutorial 1`] = `
 <Fragment>
   <AzurePipelinesTutorial
+    baseUrl="http://localhost:9000"
     component={
       Object {
         "breadcrumbs": Array [],
@@ -51,6 +52,7 @@ exports[`should render correctly: azure pipelines tutorial 1`] = `
 exports[`should render correctly: gitlab tutorial 1`] = `
 <Fragment>
   <GitLabCITutorial
+    baseUrl="http://localhost:9000"
     component={
       Object {
         "breadcrumbs": Array [],
index 225405a0fd14a13148a51054436f8af4aee603ba..e7ab59b267d0902a788abd8a07baa35db7b976fb 100644 (file)
@@ -32,6 +32,7 @@ import SaveAndRunStepContent from './SaveAndRunStepContent';
 import ServiceEndpointStepContent from './ServiceEndpointStepContent';
 
 export interface AzurePipelinesTutorialProps {
+  baseUrl: string;
   component: T.Component;
   currentUser: T.LoggedInUser;
   projectBinding: ProjectAlmBindingResponse;
@@ -51,7 +52,7 @@ interface Step {
 }
 
 export default function AzurePipelinesTutorial(props: AzurePipelinesTutorialProps) {
-  const { component, currentUser, projectBinding } = props;
+  const { baseUrl, component, currentUser, projectBinding } = props;
 
   const [currentStep, setCurrentStep] = React.useState(Steps.ExtensionInstallation);
   const [isCurrentStepValid, setIsCurrentStepValid] = React.useState(false);
@@ -69,7 +70,13 @@ export default function AzurePipelinesTutorial(props: AzurePipelinesTutorialProp
     { step: Steps.ExtensionInstallation, content: <ExtensionInstallationStepContent /> },
     {
       step: Steps.ServiceEndpoint,
-      content: <ServiceEndpointStepContent component={component} currentUser={currentUser} />
+      content: (
+        <ServiceEndpointStepContent
+          baseUrl={baseUrl}
+          component={component}
+          currentUser={currentUser}
+        />
+      )
     },
     {
       step: Steps.BranchAnalysis,
index a5d7daafdcbedee5b2c30aa51318d479bc7e1e52..04254a2482b5c25b604953298470be362581add2 100644 (file)
@@ -22,17 +22,17 @@ import { FormattedMessage } from 'react-intl';
 import { Button } from 'sonar-ui-common/components/controls/buttons';
 import { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getHostUrl } from 'sonar-ui-common/helpers/urls';
 import EditTokenModal from '../components/EditTokenModal';
 import SentenceWithHighlights from '../components/SentenceWithHighlights';
 
 export interface ServiceEndpointStepProps {
+  baseUrl: string;
   component: T.Component;
   currentUser: T.LoggedInUser;
 }
 
 export default function ServiceEndpointStepContent(props: ServiceEndpointStepProps) {
-  const { component, currentUser } = props;
+  const { baseUrl, component, currentUser } = props;
 
   const [isModalVisible, toggleModal] = React.useState(false);
 
@@ -61,8 +61,8 @@ export default function ServiceEndpointStepContent(props: ServiceEndpointStepPro
             )}
             id="onboarding.tutorial.with.azure_pipelines.ServiceEndpoint.step4.sentence"
             values={{
-              url: <code className="rule">{getHostUrl()}</code>,
-              button: <ClipboardIconButton copyValue={getHostUrl()} />
+              url: <code className="rule">{baseUrl}</code>,
+              button: <ClipboardIconButton copyValue={baseUrl} />
             }}
           />
         </li>
index bb41f38cbc11434f1f6898732c54e455c2ec145e..1ff63b595697b14c56e1163fb0ef7da41db18894 100644 (file)
@@ -101,6 +101,7 @@ it('should open a step when user click on it', () => {
 function shallowRender(props: Partial<AzurePipelinesTutorialProps> = {}) {
   return shallow<AzurePipelinesTutorialProps>(
     <AzurePipelinesTutorial
+      baseUrl="http://localhost:9000"
       component={mockComponent()}
       currentUser={mockLoggedInUser()}
       projectBinding={mockProjectAzureBindingResponse()}
index 604bd6085fe2c3f0f7447704e5cfacf71cb4c181..0f4f849718fd565ea6bfa92e3ee8885d757f5d3b 100644 (file)
@@ -46,6 +46,10 @@ it('should render open/close the token modal when asked to', () => {
 
 function shallowRender() {
   return shallow(
-    <ServiceEndpointStepContent component={mockComponent()} currentUser={mockLoggedInUser()} />
+    <ServiceEndpointStepContent
+      baseUrl="http://localhost:9000"
+      component={mockComponent()}
+      currentUser={mockLoggedInUser()}
+    />
   );
 }
index 1596e44fa1cfd33c86bab0b8d5fd6b745849d79c..7b52581aa1289e131f9be1aa05bfa4e4e894fb52 100644 (file)
@@ -35,12 +35,12 @@ exports[`should render correctly 1`] = `
         values={
           Object {
             "button": <ClipboardIconButton
-              copyValue="http://localhost"
+              copyValue="http://localhost:9000"
             />,
             "url": <code
               className="rule"
             >
-              http://localhost
+              http://localhost:9000
             </code>,
           }
         }
index 02934fa507a554790b814b6e0bf1dea230241438..47e02fc1dc13cb6286437560086a27a5ae6a3dad 100644 (file)
@@ -22,11 +22,11 @@ import { FormattedMessage } from 'react-intl';
 import { Button } from 'sonar-ui-common/components/controls/buttons';
 import { ClipboardIconButton } from 'sonar-ui-common/components/controls/clipboard';
 import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getHostUrl } from 'sonar-ui-common/helpers/urls';
 import EditTokenModal from '../components/EditTokenModal';
 import Step from '../components/Step';
 
 export interface EnvironmentVariablesStepProps {
+  baseUrl: string;
   component: T.Component;
   currentUser: T.LoggedInUser;
   finished: boolean;
@@ -40,7 +40,7 @@ const pipelineDescriptionLinkLabel = translate(
 );
 
 export default function EnvironmentVariablesStep(props: EnvironmentVariablesStepProps) {
-  const { component, currentUser, finished, open } = props;
+  const { baseUrl, component, currentUser, finished, open } = props;
 
   const [isModalVisible, toggleModal] = React.useState(false);
 
@@ -139,9 +139,9 @@ export default function EnvironmentVariablesStep(props: EnvironmentVariablesStep
             defaultMessage={fieldValueTranslation}
             id="onboarding.tutorial.with.gitlab_ci.env_variables.step2"
             values={{
-              extra: <ClipboardIconButton copyValue={getHostUrl()} />,
+              extra: <ClipboardIconButton copyValue={baseUrl} />,
               field: translate('onboarding.tutorial.with.gitlab_ci.env_variables.step2'),
-              value: <code className="rule">{getHostUrl()}</code>
+              value: <code className="rule">{baseUrl}</code>
             }}
           />
         </li>
index f7f08f40d94c85baf800e464270d8607d24f57d1..0d53961bb59f4564afffd52fcc27f82ee15eb89a 100644 (file)
@@ -36,13 +36,14 @@ export enum Steps {
 }
 
 export interface GitLabCITutorialProps {
+  baseUrl: string;
   component: T.Component;
   currentUser: T.LoggedInUser;
   projectBinding: ProjectAlmBindingResponse;
 }
 
 export default function GitLabCITutorial(props: GitLabCITutorialProps) {
-  const { component, currentUser, projectBinding } = props;
+  const { baseUrl, component, currentUser, projectBinding } = props;
 
   const [step, setStep] = React.useState(Steps.PROJECT_KEY);
   const [buildTool, setBuildTool] = React.useState<BuildTools | undefined>();
@@ -71,6 +72,7 @@ export default function GitLabCITutorial(props: GitLabCITutorialProps) {
       />
 
       <EnvironmentVariablesStep
+        baseUrl={baseUrl}
         component={component}
         currentUser={currentUser}
         finished={step > Steps.ENV_VARIABLES}
index 57cf4a4be3c8c86e038213b75e7d51746bfbc139..d01b5e292e92526de5afa295a5b34b533e1be36c 100644 (file)
@@ -34,6 +34,7 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<EnvironmentVariablesStepProps> = {}) {
   return shallow<EnvironmentVariablesStepProps>(
     <EnvironmentVariablesStep
+      baseUrl="http://localhost:9000"
       currentUser={mockLoggedInUser()}
       component={mockComponent()}
       finished={false}
index 1e2b42dce5d0c9463bfe21876e4d61f9983ac402..0fb15cf2bf35630054fd6d5709248b4e640ff049 100644 (file)
@@ -36,6 +36,7 @@ it('should render correctly', () => {
 function shallowRender(props: Partial<GitLabCITutorialProps> = {}) {
   return shallow<GitLabCITutorialProps>(
     <GitLabCITutorial
+      baseUrl="http://localhost:9000"
       component={mockComponent()}
       currentUser={mockLoggedInUser()}
       projectBinding={mockProjectGitLabBindingResponse()}
index d37c402146397e67ce9d52745d072b515cb7fe5c..c1e8ff710f1a85ff8394e1150d660e2353c10e57 100644 (file)
@@ -140,13 +140,13 @@ exports[`should render correctly: initial content 1`] = `
         values={
           Object {
             "extra": <ClipboardIconButton
-              copyValue="http://localhost"
+              copyValue="http://localhost:9000"
             />,
             "field": "onboarding.tutorial.with.gitlab_ci.env_variables.step2",
             "value": <code
               className="rule"
             >
-              http://localhost
+              http://localhost:9000
             </code>,
           }
         }
index 00c4ef25f2c15df57c91b84a63d5bc0a3ffe87c3..e7e458b4f47f74b3f8053f6bf5f3ad6a5c1341c6 100644 (file)
@@ -41,6 +41,7 @@ exports[`should render correctly 1`] = `
     setBuildTool={[Function]}
   />
   <EnvironmentVariablesStep
+    baseUrl="http://localhost:9000"
     component={
       Object {
         "breadcrumbs": Array [],
index c284c1689a288213b993eba45843c4b50f046fde..5626af2b7097c532f076469669c62f00ac12c2b6 100644 (file)
@@ -19,5 +19,6 @@
  */
 export const enum SettingsKey {
   DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
-  DefaultProjectVisibility = 'projects.default.visibility'
+  DefaultProjectVisibility = 'projects.default.visibility',
+  ServerBaseUrl = 'sonar.core.serverBaseURL'
 }