From: Jeremy Davis Date: Mon, 16 Nov 2020 14:00:59 +0000 (+0100) Subject: SONAR-14057 Display Azure Projects and Repositories X-Git-Tag: 8.6.0.39681~73 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=942a20cb4e5e3c254a08a8c56b228af1d7a5f744;p=sonarqube.git SONAR-14057 Display Azure Projects and Repositories --- diff --git a/server/sonar-web/src/main/js/api/alm-integrations.ts b/server/sonar-web/src/main/js/api/alm-integrations.ts index e013a3721df..3824f97bb2c 100644 --- a/server/sonar-web/src/main/js/api/alm-integrations.ts +++ b/server/sonar-web/src/main/js/api/alm-integrations.ts @@ -20,6 +20,8 @@ import { get, getJSON, post, postJSON } from 'sonar-ui-common/helpers/request'; import throwGlobalError from '../app/utils/throwGlobalError'; import { + AzureProject, + AzureRepository, BitbucketProject, BitbucketRepository, GithubOrganization, @@ -44,6 +46,21 @@ export function checkPersonalAccessTokenIsValid(almSetting: string): Promise { + return getJSON('/api/alm_integrations/list_azure_projects', { almSetting }).catch( + throwGlobalError + ); +} + +export function getAzureRepositories( + almSetting: string, + projectName: string +): Promise<{ repositories: AzureRepository[] }> { + return getJSON('/api/alm_integrations/search_azure_repos', { almSetting, projectName }).catch( + throwGlobalError + ); +} + export function getBitbucketServerProjects( almSetting: string ): Promise<{ projects: BitbucketProject[] }> { diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx new file mode 100644 index 00000000000..20c4e079243 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx @@ -0,0 +1,109 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 classNames from 'classnames'; +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion'; +import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { AzureProject, AzureRepository } from '../../../types/alm-integration'; +import { CreateProjectModes } from './types'; + +export interface AzureProjectAccordionProps { + loading: boolean; + onOpen: (key: string) => void; + startsOpen: boolean; + project: AzureProject; + repositories?: AzureRepository[]; +} + +const PAGE_SIZE = 30; + +export default function AzureProjectAccordion(props: AzureProjectAccordionProps) { + const { loading, startsOpen, project, repositories = [] } = props; + + const [open, setOpen] = React.useState(startsOpen); + const handleClick = () => { + if (!open) { + props.onOpen(project.key); + } + setOpen(!open); + }; + + const [page, setPage] = React.useState(1); + const limitedRepositories = repositories.slice(0, page * PAGE_SIZE); + + return ( + {project.name}}> + {open && ( + + {/* The extra loading guard is to prevent the flash of the Alert */} + {!loading && repositories.length === 0 ? ( + + + {translate('onboarding.create_project.update_your_token')} + + ) + }} + /> + + ) : ( + <> +
+ {limitedRepositories.map(repo => ( +
+ + {repo.name} + +
+ ))} +
+ setPage(p => p + 1)} + /> + + )} +
+ )} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx index 292abc2e548..7cd34c6a42a 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx @@ -21,12 +21,15 @@ import * as React from 'react'; import { WithRouterProps } from 'react-router'; import { checkPersonalAccessTokenIsValid, + getAzureProjects, + getAzureRepositories, setAlmPersonalAccessToken } from '../../../api/alm-integrations'; +import { AzureProject, AzureRepository } from '../../../types/alm-integration'; import { AlmSettingsInstance } from '../../../types/alm-settings'; import AzureCreateProjectRenderer from './AzureProjectCreateRenderer'; -interface Props extends Pick { +interface Props extends Pick { canAdmin: boolean; loadingBindings: boolean; onProjectCreate: (projectKeys: string[]) => void; @@ -35,7 +38,10 @@ interface Props extends Pick { interface State { loading: boolean; + loadingRepositories: T.Dict; patIsValid?: boolean; + projects?: AzureProject[]; + repositories: T.Dict; settings?: AlmSettingsInstance; submittingToken?: boolean; tokenValidationFailed: boolean; @@ -51,6 +57,8 @@ export default class AzureProjectCreate extends React.PureComponent false); + let projects: AzureProject[] | undefined; + if (patIsValid) { + projects = await this.fetchAzureProjects(); + } + + const { repositories } = this.state; + + let firstProjectKey: string; + + if (projects && projects.length > 0) { + firstProjectKey = projects[0].key; + + this.setState(({ loadingRepositories }) => ({ + loadingRepositories: { ...loadingRepositories, [firstProjectKey]: true } + })); + + const repos = await this.fetchAzureRepositories(firstProjectKey); + repositories[firstProjectKey] = repos; + } + if (this.mounted) { - this.setState({ - patIsValid, - loading: false + this.setState(({ loadingRepositories }) => { + if (firstProjectKey) { + loadingRepositories[firstProjectKey] = false; + } + + return { + patIsValid, + loading: false, + loadingRepositories: { ...loadingRepositories }, + projects, + repositories + }; }); } }; + fetchAzureProjects = (): Promise => { + const { settings } = this.state; + + if (!settings) { + return Promise.resolve(undefined); + } + + return getAzureProjects(settings.key).then(({ projects }) => projects); + }; + + fetchAzureRepositories = (projectKey: string): Promise => { + const { settings } = this.state; + + if (!settings) { + return Promise.resolve([]); + } + + return getAzureRepositories(settings.key, projectKey) + .then(({ repositories }) => repositories) + .catch(() => []); + }; + + cleanUrl = () => { + const { location, router } = this.props; + delete location.query.resetPat; + router.replace(location); + }; + + handleOpenProject = async (projectKey: string) => { + this.setState(({ loadingRepositories }) => ({ + loadingRepositories: { ...loadingRepositories, [projectKey]: true } + })); + + const projectRepos = await this.fetchAzureRepositories(projectKey); + + this.setState(({ loadingRepositories, repositories }) => ({ + loadingRepositories: { ...loadingRepositories, [projectKey]: false }, + repositories: { ...repositories, [projectKey]: projectRepos } + })); + }; + checkPersonalAccessToken = () => { const { settings } = this.state; @@ -114,7 +192,7 @@ export default class AzureProjectCreate extends React.PureComponent; + onOpenProject: (key: string) => void; onPersonalAccessTokenCreate: (token: string) => void; + projects?: AzureProject[]; + repositories: T.Dict; settings?: AlmSettingsInstance; showPersonalAccessTokenForm?: boolean; submittingToken?: boolean; @@ -40,6 +45,9 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend const { canAdmin, loading, + loadingRepositories, + projects, + repositories, showPersonalAccessTokenForm, settings, submittingToken, @@ -80,7 +88,12 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend /> ) : ( - + ))} ); diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx b/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx index c6f34ede827..f61257b5711 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx @@ -18,14 +18,71 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { AzureProject, AzureRepository } from '../../../types/alm-integration'; +import AzureProjectAccordion from './AzureProjectAccordion'; +import { CreateProjectModes } from './types'; -export interface AzureProjectsListProps {} +export interface AzureProjectsListProps { + loadingRepositories: T.Dict; + onOpenProject: (key: string) => void; + projects?: AzureProject[]; + repositories: T.Dict; +} + +const PAGE_SIZE = 10; + +export default function AzureProjectsList(props: AzureProjectsListProps) { + const { loadingRepositories, projects = [], repositories } = props; + + const [page, setPage] = React.useState(1); + + if (projects.length === 0) { + return ( + + + {translate('onboarding.create_project.update_your_token')} + + ) + }} + /> + + ); + } + + const filteredProjects = projects.slice(0, page * PAGE_SIZE); -export default function AzureProjectsList(_props: AzureProjectsListProps) { return (
- Coming soon! + {filteredProjects.map((p, i) => ( + + ))} + + setPage(p => p + 1)} + total={projects.length} + />
); } diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx index a2cbadb2912..60c5abe3ce4 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx @@ -130,6 +130,7 @@ export class CreateProjectPage extends React.PureComponent { loadingBindings={loading} location={location} onProjectCreate={this.handleProjectCreate} + router={router} settings={azureSettings} /> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectAccordion-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectAccordion-test.tsx new file mode 100644 index 00000000000..b9e7f3012b6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectAccordion-test.tsx @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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 BoxedGroupAccordion from 'sonar-ui-common/components/controls/BoxedGroupAccordion'; +import { mockAzureProject, mockAzureRepository } from '../../../../helpers/mocks/alm-integrations'; +import AzureProjectAccordion, { AzureProjectAccordionProps } from '../AzureProjectAccordion'; + +it('should render correctly', () => { + expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); + expect(shallowRender({ startsOpen: false })).toMatchSnapshot('closed'); + expect(shallowRender({ repositories: [mockAzureRepository()] })).toMatchSnapshot( + 'with a repository' + ); +}); + +it('should open when clicked', () => { + const onOpen = jest.fn(); + + const wrapper = shallowRender({ + onOpen, + repositories: [mockAzureRepository()], + startsOpen: false + }); + expect( + wrapper + .find(BoxedGroupAccordion) + .children() + .exists() + ).toBe(false); + + wrapper + .find(BoxedGroupAccordion) + .props() + .onClick(); + + expect(onOpen).toBeCalled(); + + expect( + wrapper + .find(BoxedGroupAccordion) + .children() + .exists() + ).toBe(true); +}); + +it('should close when clicked', () => { + const onOpen = jest.fn(); + + const wrapper = shallowRender({ + onOpen, + repositories: [mockAzureRepository()] + }); + + expect( + wrapper + .find(BoxedGroupAccordion) + .children() + .exists() + ).toBe(true); + + wrapper + .find(BoxedGroupAccordion) + .props() + .onClick(); + + expect(onOpen).not.toBeCalled(); + + expect( + wrapper + .find(BoxedGroupAccordion) + .children() + .exists() + ).toBe(false); +}); + +function shallowRender(overrides: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreate-test.tsx index 7ce89327f6e..c40406179e0 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreate-test.tsx @@ -23,17 +23,22 @@ import * as React from 'react'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { checkPersonalAccessTokenIsValid, + getAzureProjects, + getAzureRepositories, setAlmPersonalAccessToken } from '../../../../api/alm-integrations'; +import { mockAzureProject, mockAzureRepository } from '../../../../helpers/mocks/alm-integrations'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; -import { mockLocation } from '../../../../helpers/testMocks'; +import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; import { AlmKeys } from '../../../../types/alm-settings'; import AzureProjectCreate from '../AzureProjectCreate'; jest.mock('../../../../api/alm-integrations', () => { return { checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true), - setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null) + setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null), + getAzureProjects: jest.fn().mockResolvedValue({ projects: [] }), + getAzureRepositories: jest.fn().mockResolvedValue({ repositories: [] }) }; }); @@ -66,7 +71,8 @@ it('should correctly handle an invalid PAT', async () => { }); it('should correctly handle setting a new PAT', async () => { - const wrapper = shallowRender(); + const router = mockRouter(); + const wrapper = shallowRender({ router }); wrapper.instance().handlePersonalAccessTokenCreate('token'); expect(setAlmPersonalAccessToken).toBeCalledWith('foo', 'token'); expect(wrapper.state().submittingToken).toBe(true); @@ -76,6 +82,59 @@ it('should correctly handle setting a new PAT', async () => { expect(checkPersonalAccessTokenIsValid).toBeCalled(); expect(wrapper.state().submittingToken).toBe(false); expect(wrapper.state().tokenValidationFailed).toBe(true); + + // Try again, this time with a correct token: + + wrapper.instance().handlePersonalAccessTokenCreate('correct token'); + await waitAndUpdate(wrapper); + expect(wrapper.state().tokenValidationFailed).toBe(false); + expect(router.replace).toBeCalled(); +}); + +it('should correctly fetch projects and repositories on mount', async () => { + const project = mockAzureProject(); + (getAzureProjects as jest.Mock).mockResolvedValueOnce({ projects: [project] }); + (getAzureRepositories as jest.Mock).mockResolvedValueOnce({ + repositories: [mockAzureRepository()] + }); + + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + expect(getAzureProjects).toBeCalled(); + expect(getAzureRepositories).toBeCalledTimes(1); + expect(getAzureRepositories).toBeCalledWith('foo', project.key); +}); + +it('should handle opening a project', async () => { + const projects = [ + mockAzureProject(), + mockAzureProject({ key: 'project2', name: 'Project to open' }) + ]; + + const firstProjectRepos = [mockAzureRepository()]; + const secondProjectRepos = [mockAzureRepository({ projectName: projects[1].name })]; + + (getAzureProjects as jest.Mock).mockResolvedValueOnce({ projects }); + (getAzureRepositories as jest.Mock) + .mockResolvedValueOnce({ + repositories: firstProjectRepos + }) + .mockResolvedValueOnce({ + repositories: secondProjectRepos + }); + + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + + wrapper.instance().handleOpenProject(projects[1].key); + await waitAndUpdate(wrapper); + + expect(getAzureRepositories).toBeCalledWith('foo', projects[1].key); + + expect(wrapper.state().repositories).toEqual({ + [projects[0].key]: firstProjectRepos, + [projects[1].key]: secondProjectRepos + }); }); function shallowRender(overrides: Partial = {}) { @@ -85,6 +144,7 @@ function shallowRender(overrides: Partial = {}) { loadingBindings={false} location={mockLocation()} onProjectCreate={jest.fn()} + router={mockRouter()} settings={[mockAlmSettingsInstance({ alm: AlmKeys.Azure, key: 'foo' })]} {...overrides} /> diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx index d38e64d922e..9e09b0a1810 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreateRenderer-test.tsx @@ -20,6 +20,7 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockAzureProject, mockAzureRepository } from '../../../../helpers/mocks/alm-integrations'; import { mockAlmSettingsInstance } from '../../../../helpers/mocks/alm-settings'; import { AlmKeys } from '../../../../types/alm-settings'; import AzureProjectCreateRenderer, { @@ -34,11 +35,17 @@ it('should render correctly', () => { }); function shallowRender(overrides: Partial) { + const project = mockAzureProject(); + return shallow( { + expect(shallowRender({})).toMatchSnapshot('default'); + expect(shallowRender({ projects: [] })).toMatchSnapshot('empty'); +}); + +it('should handle pagination', () => { + const projects = new Array(21) + .fill(1) + .map((_, i) => mockAzureProject({ key: `project-${i}`, name: `Project #${i}` })); + + const wrapper = shallowRender({ projects }); + + expect(wrapper.find(AzureProjectAccordion)).toHaveLength(10); + + wrapper.find(ListFooter).props().loadMore!(); + + expect(wrapper.find(AzureProjectAccordion)).toHaveLength(20); +}); + +function shallowRender(overrides: Partial = {}) { + const project = mockAzureProject(); + + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap new file mode 100644 index 00000000000..0d0e57cdbde --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectAccordion-test.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: closed 1`] = ` + + Azure Project + + } +/> +`; + +exports[`should render correctly: loading 1`] = ` + + Azure Project + + } +> + +
+ + + +`; + +exports[`should render correctly: with a repository 1`] = ` + + Azure Project + + } +> + +
+
+ + Azure repo 1 + +
+
+ +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap index 47f70559c9f..40a022b1e4c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectCreate-test.tsx.snap @@ -4,7 +4,10 @@ exports[`should render correctly 1`] = ` } /> - + `; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap new file mode 100644 index 00000000000..8711c8e646b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: default 1`] = ` +
+ + +
+`; + +exports[`should render correctly: empty 1`] = ` + + + onboarding.create_project.update_your_token + , + } + } + /> + +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap index 7f37890764c..970ce4d6c59 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap @@ -85,6 +85,19 @@ exports[`should render correctly if the Azure method is selected 1`] = ` } } onProjectCreate={[Function]} + router={ + Object { + "createHref": [MockFunction], + "createPath": [MockFunction], + "go": [MockFunction], + "goBack": [MockFunction], + "goForward": [MockFunction], + "isActive": [MockFunction], + "push": [MockFunction], + "replace": [MockFunction], + "setRouteLeaveHook": [MockFunction], + } + } settings={Array []} />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap index 99c2341cba2..48b2949cad4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AzureForm-test.tsx.snap @@ -16,7 +16,7 @@ exports[`should render correctly: create 1`] = ` settings.almintegration.form.url.azure.help
- https://ado.your-company.com/ + https://ado.your-company.com/DefaultCollection } @@ -53,7 +53,7 @@ exports[`should render correctly: edit 1`] = ` settings.almintegration.form.url.azure.help
- https://ado.your-company.com/ + https://ado.your-company.com/DefaultCollection } diff --git a/server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts b/server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts index 1fdd37225e6..1196150f464 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/alm-integrations.ts @@ -18,12 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { + AzureProject, + AzureRepository, BitbucketProject, BitbucketRepository, GithubRepository, GitlabProject } from '../../types/alm-integration'; +export function mockAzureProject(overrides: Partial = {}): AzureProject { + return { + key: 'azure-project-1', + name: 'Azure Project', + ...overrides + }; +} + +export function mockAzureRepository(overrides: Partial = {}): AzureRepository { + return { + name: 'Azure repo 1', + projectName: 'Azure Project', + ...overrides + }; +} + export function mockBitbucketProject(overrides: Partial = {}): BitbucketProject { return { id: 1, diff --git a/server/sonar-web/src/main/js/types/alm-integration.ts b/server/sonar-web/src/main/js/types/alm-integration.ts index cf70f73017a..fca78f50149 100644 --- a/server/sonar-web/src/main/js/types/alm-integration.ts +++ b/server/sonar-web/src/main/js/types/alm-integration.ts @@ -17,6 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +export interface AzureProject { + key: string; + name: string; +} + +export interface AzureRepository { + name: string; + projectName: string; +} + export interface BitbucketProject { id: number; key: string; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 02ee1bf0c49..36cda900b13 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3287,6 +3287,8 @@ onboarding.create_project.import_selected_repo=Set up selected repository onboarding.create_project.go_to_project=Go to project onboarding.create_project.azure.title=Which Azure DevOps Server repository do you want to set up? +onboarding.create_project.azure.no_projects=No projects could be fetched from Azure DevOps Server. Contact your system administrator, or {link}. +onboarding.create_project.azure.no_repositories=Could not fetch repositories for this project. Contact your system administrator, or {link}. onboarding.create_project.github.title=Which GitHub repository do you want to set up? onboarding.create_project.github.choose_organization=Choose organization onboarding.create_project.github.warning.title=Could not connect to GitHub