*/
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';
interface State {
almBinding?: AlmBindingDefinition;
+ baseUrl: string;
forceManual: boolean;
loading: boolean;
projectBinding?: ProjectAlmBindingResponse;
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 () => {
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,
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
return (
<TutorialSelectionRenderer
almBinding={almBinding}
+ baseUrl={baseUrl}
component={component}
currentUser={currentUser}
loading={loading}
export interface TutorialSelectionRendererProps {
almBinding?: AlmBindingDefinition;
+ baseUrl: string;
component: T.Component;
currentUser: T.LoggedInUser;
loading: boolean;
}
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" />;
{selectedTutorial === TutorialModes.GitLabCI && projectBinding !== undefined && (
<GitLabCITutorial
+ baseUrl={baseUrl}
component={component}
currentUser={currentUser}
projectBinding={projectBinding}
{selectedTutorial === TutorialModes.AzurePipelines && projectBinding !== undefined && (
<AzurePipelinesTutorial
+ baseUrl={baseUrl}
component={component}
currentUser={currentUser}
projectBinding={projectBinding}
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,
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', () => {
);
});
+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
return shallow<TutorialSelectionRendererProps>(
<TutorialSelectionRenderer
almBinding={mockBitbucketBindingDefinition()}
+ baseUrl="http://localhost:9000"
component={mockComponent()}
currentUser={mockLoggedInUser()}
loading={false}
exports[`should render correctly 1`] = `
<TutorialSelectionRenderer
+ baseUrl="http://host.url"
component={
Object {
"breadcrumbs": Array [],
exports[`should render correctly: azure pipelines tutorial 1`] = `
<Fragment>
<AzurePipelinesTutorial
+ baseUrl="http://localhost:9000"
component={
Object {
"breadcrumbs": Array [],
exports[`should render correctly: gitlab tutorial 1`] = `
<Fragment>
<GitLabCITutorial
+ baseUrl="http://localhost:9000"
component={
Object {
"breadcrumbs": Array [],
import ServiceEndpointStepContent from './ServiceEndpointStepContent';
export interface AzurePipelinesTutorialProps {
+ baseUrl: string;
component: T.Component;
currentUser: T.LoggedInUser;
projectBinding: ProjectAlmBindingResponse;
}
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);
{ 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,
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);
)}
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>
function shallowRender(props: Partial<AzurePipelinesTutorialProps> = {}) {
return shallow<AzurePipelinesTutorialProps>(
<AzurePipelinesTutorial
+ baseUrl="http://localhost:9000"
component={mockComponent()}
currentUser={mockLoggedInUser()}
projectBinding={mockProjectAzureBindingResponse()}
function shallowRender() {
return shallow(
- <ServiceEndpointStepContent component={mockComponent()} currentUser={mockLoggedInUser()} />
+ <ServiceEndpointStepContent
+ baseUrl="http://localhost:9000"
+ component={mockComponent()}
+ currentUser={mockLoggedInUser()}
+ />
);
}
values={
Object {
"button": <ClipboardIconButton
- copyValue="http://localhost"
+ copyValue="http://localhost:9000"
/>,
"url": <code
className="rule"
>
- http://localhost
+ http://localhost:9000
</code>,
}
}
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;
);
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);
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>
}
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>();
/>
<EnvironmentVariablesStep
+ baseUrl={baseUrl}
component={component}
currentUser={currentUser}
finished={step > Steps.ENV_VARIABLES}
function shallowRender(props: Partial<EnvironmentVariablesStepProps> = {}) {
return shallow<EnvironmentVariablesStepProps>(
<EnvironmentVariablesStep
+ baseUrl="http://localhost:9000"
currentUser={mockLoggedInUser()}
component={mockComponent()}
finished={false}
function shallowRender(props: Partial<GitLabCITutorialProps> = {}) {
return shallow<GitLabCITutorialProps>(
<GitLabCITutorial
+ baseUrl="http://localhost:9000"
component={mockComponent()}
currentUser={mockLoggedInUser()}
projectBinding={mockProjectGitLabBindingResponse()}
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>,
}
}
setBuildTool={[Function]}
/>
<EnvironmentVariablesStep
+ baseUrl="http://localhost:9000"
component={
Object {
"breadcrumbs": Array [],
*/
export const enum SettingsKey {
DaysBeforeDeletingInactiveBranchesAndPRs = 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
- DefaultProjectVisibility = 'projects.default.visibility'
+ DefaultProjectVisibility = 'projects.default.visibility',
+ ServerBaseUrl = 'sonar.core.serverBaseURL'
}