From 6e7ab27398db34433cc1202379a574bf4342c301 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 27 Jul 2020 17:30:15 +0200 Subject: [PATCH] SONAR-13629 Display gitlab projects --- .../src/main/js/api/alm-integrations.ts | 30 ++- .../apps/create/project/CreateProjectPage.tsx | 1 + .../create/project/GitHubProjectCreate.tsx | 4 +- .../create/project/GitlabProjectCreate.tsx | 156 ++++++++++-- .../project/GitlabProjectCreateRenderer.tsx | 24 +- .../project/GitlabProjectSelectionForm.tsx | 149 +++++++++++ .../__tests__/GitHubProjectCreate-test.tsx | 4 +- .../__tests__/GitlabProjectCreate-test.tsx | 105 +++++++- .../GitlabProjectCreateRenderer-test.tsx | 10 + .../GitlabProjectSelectionForm-test.tsx | 68 +++++ .../CreateProjectPage-test.tsx.snap | 13 + .../GitlabProjectCreate-test.tsx.snap | 12 + .../GitlabProjectCreateRenderer-test.tsx.snap | 34 +++ .../GitlabProjectSelectionForm-test.tsx.snap | 234 ++++++++++++++++++ .../src/main/js/apps/create/project/style.css | 17 ++ .../main/js/helpers/mocks/alm-integrations.ts | 16 +- .../src/main/js/types/alm-integration.ts | 11 + .../resources/org/sonar/l10n/core.properties | 3 + 18 files changed, 851 insertions(+), 40 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectSelectionForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap 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 b0007b3c480..51914405af5 100644 --- a/server/sonar-web/src/main/js/api/alm-integrations.ts +++ b/server/sonar-web/src/main/js/api/alm-integrations.ts @@ -23,7 +23,8 @@ import { BitbucketProject, BitbucketRepository, GithubOrganization, - GithubRepository + GithubRepository, + GitlabProject } from '../types/alm-integration'; import { ProjectBase } from './components'; @@ -120,16 +121,33 @@ export function getGithubOrganizations( export function getGithubRepositories(data: { almSetting: string; organization: string; - ps: number; - p?: number; + pageSize: number; + page?: number; query?: string; }): Promise<{ repositories: GithubRepository[]; paging: T.Paging }> { - const { almSetting, organization, ps, p = 1, query } = data; + const { almSetting, organization, pageSize, page = 1, query } = data; return getJSON('/api/alm_integrations/list_github_repositories', { almSetting, organization, - p, - ps, + p: page, + ps: pageSize, q: query || undefined }).catch(throwGlobalError); } + +export function getGitlabProjects(data: { + almSetting: string; + page?: number; + pageSize?: number; + query?: string; +}): Promise<{ projects: GitlabProject[]; projectsPaging: T.Paging }> { + const { almSetting, pageSize, page, query } = data; + return getJSON('/api/alm_integrations/search_gitlab_repos', { + almSetting, + projectName: query || undefined, + p: page, + ps: pageSize + }) + .then(({ repositories, paging }) => ({ projects: repositories, projectsPaging: paging })) + .catch(throwGlobalError); +} 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 5b79dd82e6b..3976e1acb93 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 @@ -138,6 +138,7 @@ export class CreateProjectPage extends React.PureComponent { loadingBindings={loading} location={location} onProjectCreate={this.handleProjectCreate} + router={router} settings={gitlabSettings} /> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx index 1c03f8c3b50..f438d2486d5 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx @@ -176,8 +176,8 @@ export default class GitHubProjectCreate extends React.Component { const data = await getGithubRepositories({ almSetting: settings.key, organization: organizationKey, - ps: REPOSITORY_PAGE_SIZE, - p: page, + pageSize: REPOSITORY_PAGE_SIZE, + page, query }); diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx index 33d05358658..d7f2ad37283 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx @@ -21,12 +21,14 @@ import * as React from 'react'; import { WithRouterProps } from 'react-router'; import { checkPersonalAccessTokenIsValid, + getGitlabProjects, setAlmPersonalAccessToken } from '../../../api/alm-integrations'; +import { GitlabProject } from '../../../types/alm-integration'; import { AlmSettingsInstance } from '../../../types/alm-settings'; import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer'; -interface Props extends Pick { +interface Props extends Pick { canAdmin: boolean; loadingBindings: boolean; onProjectCreate: (projectKeys: string[]) => void; @@ -35,20 +37,32 @@ interface Props extends Pick { interface State { loading: boolean; + loadingMore: boolean; + projects?: GitlabProject[]; + projectsPaging: T.Paging; submittingToken: boolean; tokenIsValid: boolean; tokenValidationFailed: boolean; + searching: boolean; + searchQuery: string; settings?: AlmSettingsInstance; } +const GITLAB_PROJECTS_PAGESIZE = 30; + export default class GitlabProjectCreate extends React.PureComponent { mounted = false; constructor(props: Props) { super(props); + this.state = { loading: false, + loadingMore: false, + projectsPaging: { pageIndex: 1, total: 0, pageSize: GITLAB_PROJECTS_PAGESIZE }, tokenIsValid: false, + searching: false, + searchQuery: '', settings: props.settings.length === 1 ? props.settings[0] : undefined, submittingToken: false, tokenValidationFailed: false @@ -78,11 +92,27 @@ export default class GitlabProjectCreate extends React.PureComponent false); }; - handlePersonalAccessTokenCreate = (token: string) => { + fetchProjects = (pageIndex = 1, query?: string) => { + const { settings } = this.state; + + if (!settings) { + return Promise.resolve(undefined); + } + + return getGitlabProjects({ + almSetting: settings.key, + page: pageIndex, + pageSize: GITLAB_PROJECTS_PAGESIZE, + query + }).catch(() => undefined); + }; + + handleLoadMore = async () => { + this.setState({ loadingMore: true }); + + const { + projectsPaging: { pageIndex }, + searchQuery + } = this.state; + + const result = await this.fetchProjects(pageIndex + 1, searchQuery); + + if (this.mounted) { + this.setState(({ projects = [], projectsPaging }) => ({ + loadingMore: false, + projects: result ? [...projects, ...result.projects] : projects, + projectsPaging: result ? result.projectsPaging : projectsPaging + })); + } + }; + + handleSearch = async (searchQuery: string) => { + this.setState({ searching: true, searchQuery }); + + const result = await this.fetchProjects(1, searchQuery); + + if (this.mounted) { + this.setState(({ projects, projectsPaging }) => ({ + searching: false, + projects: result ? result.projects : projects, + projectsPaging: result ? result.projectsPaging : projectsPaging + })); + } + }; + + cleanUrl = () => { + const { location, router } = this.props; + delete location.query.resetPat; + router.replace(location); + }; + + handlePersonalAccessTokenCreate = async (token: string) => { const { settings } = this.state; if (!settings || token.length < 1) { @@ -104,37 +188,59 @@ export default class GitlabProjectCreate extends React.PureComponent { - if (this.mounted) { - this.setState({ - submittingToken: false, - tokenIsValid: patIsValid, - tokenValidationFailed: !patIsValid - }); - if (patIsValid) { - this.fetchInitialData(); - } - } - }) - .catch(() => { - if (this.mounted) { - this.setState({ submittingToken: false }); + + try { + await setAlmPersonalAccessToken(settings.key, token); + + const patIsValid = await this.checkPersonalAccessToken(); + + if (this.mounted) { + this.setState({ + submittingToken: false, + tokenIsValid: patIsValid, + tokenValidationFailed: !patIsValid + }); + + if (patIsValid) { + this.cleanUrl(); + await this.fetchInitialData(); } - }); + } + } catch (e) { + if (this.mounted) { + this.setState({ submittingToken: false }); + } + } }; render() { const { canAdmin, loadingBindings, location } = this.props; - const { loading, tokenIsValid, settings, submittingToken, tokenValidationFailed } = this.state; + const { + loading, + loadingMore, + projects, + projectsPaging, + tokenIsValid, + searching, + searchQuery, + settings, + submittingToken, + tokenValidationFailed + } = this.state; return ( void; onPersonalAccessTokenCreate: (pat: string) => void; + onSearch: (searchQuery: string) => void; + projects?: GitlabProject[]; + projectsPaging: T.Paging; + searching: boolean; + searchQuery: string; settings?: AlmSettingsInstance; showPersonalAccessTokenForm?: boolean; submittingToken?: boolean; @@ -39,6 +48,11 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe const { canAdmin, loading, + loadingMore, + projects, + projectsPaging, + searching, + searchQuery, settings, showPersonalAccessTokenForm, submittingToken, @@ -77,7 +91,15 @@ export default function GitlabProjectCreateRenderer(props: GitlabProjectCreateRe validationFailed={tokenValidationFailed} /> ) : ( -
Token is valid!
+ ))} ); diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx new file mode 100644 index 00000000000..01da4fc8851 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx @@ -0,0 +1,149 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; +import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; +import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; +import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; +import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon'; +import DetachIcon from 'sonar-ui-common/components/icons/DetachIcon'; +import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; +import { Alert } from 'sonar-ui-common/components/ui/Alert'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getProjectUrl } from '../../../helpers/urls'; +import { GitlabProject } from '../../../types/alm-integration'; +import { ComponentQualifier } from '../../../types/component'; +import { CreateProjectModes } from './types'; + +export interface GitlabProjectSelectionFormProps { + loadingMore: boolean; + onLoadMore: () => void; + onSearch: (searchQuery: string) => void; + projects?: GitlabProject[]; + projectsPaging: T.Paging; + searching: boolean; + searchQuery: string; +} + +export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) { + const { loadingMore, projects = [], projectsPaging, searching, searchQuery } = props; + + if (projects.length === 0 && searchQuery.length === 0 && !searching) { + return ( + + + {translate('onboarding.create_project.update_your_token')} + + ) + }} + /> + + ); + } + + return ( +
+ + +
+ + {projects.length === 0 ? ( +
{translate('no_results')}
+ ) : ( + + + {projects.map(project => ( + + + + {project.sqProjectKey ? ( + <> + + + + ) : ( + + )} + + ))} + +
+ + + {project.name} + + +
+ + + {project.pathName} + + +
+ + + {translate('onboarding.create_project.gitlab.link')} + + + + + {translate('onboarding.create_project.repository_imported')}: + + +
+ + + {project.sqProjectName} + +
+
 
+ )} + +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx index 4577ba4a0df..072f321528f 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHubProjectCreate-test.tsx @@ -186,8 +186,8 @@ it('should handle search', async () => { expect(getGithubRepositories).toBeCalledWith({ almSetting: 'a', organization: 'o1', - p: 1, - ps: 30, + page: 1, + pageSize: 30, query: 'query' }); expect(wrapper.state().repositories).toEqual(repositories); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx index da88c061d27..4dffe5674f5 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreate-test.tsx @@ -23,16 +23,19 @@ import * as React from 'react'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { checkPersonalAccessTokenIsValid, + getGitlabProjects, setAlmPersonalAccessToken } from '../../../../api/alm-integrations'; +import { mockGitlabProject } 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 GitlabProjectCreate from '../GitlabProjectCreate'; jest.mock('../../../../api/alm-integrations', () => ({ checkPersonalAccessTokenIsValid: jest.fn().mockResolvedValue(true), - setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null) + setAlmPersonalAccessToken: jest.fn().mockResolvedValue(null), + getGitlabProjects: jest.fn().mockRejectedValue('error') })); beforeEach(jest.clearAllMocks); @@ -75,7 +78,12 @@ it('should correctly handle an invalid PAT', async () => { }); describe('setting a new PAT', () => { - const wrapper = shallowRender(); + const routerReplace = jest.fn(); + const wrapper = shallowRender({ router: mockRouter({ replace: routerReplace }) }); + + beforeEach(() => { + jest.clearAllMocks(); + }); it('should correctly handle it if invalid', async () => { (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(false); @@ -99,9 +107,99 @@ describe('setting a new PAT', () => { expect(checkPersonalAccessTokenIsValid).toBeCalled(); expect(wrapper.state().submittingToken).toBe(false); expect(wrapper.state().tokenValidationFailed).toBe(false); + + expect(routerReplace).toBeCalled(); }); }); +it('should fetch more projects and preserve search', async () => { + (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true); + + const projects = [ + mockGitlabProject({ id: '1' }), + mockGitlabProject({ id: '2' }), + mockGitlabProject({ id: '3' }), + mockGitlabProject({ id: '4' }), + mockGitlabProject({ id: '5' }), + mockGitlabProject({ id: '6' }) + ]; + (getGitlabProjects as jest.Mock) + .mockResolvedValueOnce({ + projects: projects.slice(0, 5), + projectsPaging: { + pageIndex: 1, + pageSize: 4, + total: 6 + } + }) + .mockResolvedValueOnce({ + projects: projects.slice(5), + projectsPaging: { + pageIndex: 2, + pageSize: 4, + total: 6 + } + }); + + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + wrapper.setState({ searchQuery: 'query' }); + + wrapper.instance().handleLoadMore(); + expect(wrapper.state().loadingMore).toBe(true); + + await waitAndUpdate(wrapper); + expect(wrapper.state().loadingMore).toBe(false); + expect(wrapper.state().projects).toEqual(projects); + + expect(getGitlabProjects).toBeCalledWith(expect.objectContaining({ query: 'query' })); +}); + +it('should search for projects', async () => { + (checkPersonalAccessTokenIsValid as jest.Mock).mockResolvedValueOnce(true); + + const projects = [ + mockGitlabProject({ id: '1' }), + mockGitlabProject({ id: '2' }), + mockGitlabProject({ id: '3' }), + mockGitlabProject({ id: '4' }), + mockGitlabProject({ id: '5' }), + mockGitlabProject({ id: '6' }) + ]; + (getGitlabProjects as jest.Mock) + .mockResolvedValueOnce({ + projects, + projectsPaging: { + pageIndex: 1, + pageSize: 6, + total: 6 + } + }) + .mockResolvedValueOnce({ + projects: projects.slice(3, 5), + projectsPaging: { + pageIndex: 1, + pageSize: 6, + total: 2 + } + }); + const query = 'query'; + + const wrapper = shallowRender(); + await waitAndUpdate(wrapper); + + wrapper.instance().handleSearch(query); + expect(wrapper.state().searching).toBe(true); + + await waitAndUpdate(wrapper); + expect(wrapper.state().searching).toBe(false); + expect(wrapper.state().searchQuery).toBe(query); + expect(wrapper.state().projects).toEqual([projects[3], projects[4]]); + + expect(getGitlabProjects).toBeCalledWith(expect.objectContaining({ query })); +}); + function shallowRender(props: Partial = {}) { return shallow( = {}) { loadingBindings={false} location={mockLocation()} onProjectCreate={jest.fn()} + router={mockRouter()} settings={[mockAlmSettingsInstance({ alm: AlmKeys.GitLab, key: almSettingKey })]} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx index c824f5ea041..08e6251ddbc 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitlabProjectCreateRenderer-test.tsx @@ -33,6 +33,9 @@ it('should render correctly', () => { 'invalid settings, admin user' ); expect(shallowRender()).toMatchSnapshot('pat form'); + expect(shallowRender({ showPersonalAccessTokenForm: false })).toMatchSnapshot( + 'project selection form' + ); }); function shallowRender(props: Partial = {}) { @@ -40,7 +43,14 @@ function shallowRender(props: Partial = {}) { { + expect(shallowRender()).toMatchSnapshot('projects'); + + expect(shallowRender({ projects: undefined, projectsPaging: mockPaging() })).toMatchSnapshot( + 'undefined projects' + ); + expect(shallowRender({ projects: [], projectsPaging: mockPaging() })).toMatchSnapshot( + 'no projects' + ); + expect( + shallowRender({ projects: [], projectsPaging: mockPaging(), searchQuery: 'findme' }) + ).toMatchSnapshot('no projects when searching'); +}); + +function shallowRender(props: Partial = {}) { + const projects = [ + mockGitlabProject(), + mockGitlabProject({ + id: '2', + sqProjectKey: 'already-imported', + sqProjectName: 'Already Imported' + }) + ]; + + return shallow( + + ); +} + +function mockPaging(total = 0) { + return { total, pageIndex: 1, pageSize: 30 }; +} 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 3c54a4b2d4d..8dd4ffc6bc8 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 @@ -174,6 +174,19 @@ exports[`should render correctly if the GitLab 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/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap index 8bf4d3701b3..08c98497cc1 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectCreate-test.tsx.snap @@ -4,7 +4,19 @@ exports[`should render correctly 1`] = ` `; + +exports[`should render correctly: project selection form 1`] = ` + + + + onboarding.create_project.gitlab.title + + } + /> + + +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap new file mode 100644 index 00000000000..eb9bf997e36 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/GitlabProjectSelectionForm-test.tsx.snap @@ -0,0 +1,234 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: no projects 1`] = ` + + + onboarding.create_project.update_your_token + , + } + } + /> + +`; + +exports[`should render correctly: no projects when searching 1`] = ` +
+ +
+
+ no_results +
+ +
+`; + +exports[`should render correctly: projects 1`] = ` +
+ +
+ + + + + + + + + + + + + + +
+ + + Awesome Project ! + + +
+ + + Company / Best Projects + + +
+ + + onboarding.create_project.gitlab.link + + +   +
+ + + Awesome Project ! + + +
+ + + Company / Best Projects + + +
+ + + onboarding.create_project.gitlab.link + + + + + onboarding.create_project.repository_imported + : + + +
+ + + Already Imported + +
+
+ +
+`; + +exports[`should render correctly: undefined projects 1`] = ` + + + onboarding.create_project.update_your_token + , + } + } + /> + +`; diff --git a/server/sonar-web/src/main/js/apps/create/project/style.css b/server/sonar-web/src/main/js/apps/create/project/style.css index 7930a3b216e..b02012dfe23 100644 --- a/server/sonar-web/src/main/js/apps/create/project/style.css +++ b/server/sonar-web/src/main/js/apps/create/project/style.css @@ -60,3 +60,20 @@ .create-project-github-repository .notice svg { color: var(--green); } + +.create-project-import-gitlab table > tbody > tr > td { + vertical-align: middle; +} + +.create-project-import-gitlab .project-name, +.create-project-import-gitlab .project-path { + max-width: 400px; +} + +.create-project-import-gitlab .sq-project-link { + max-width: 300px; +} + +.create-project-import-gitlab .already-set-up svg { + color: var(--green); +} 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 b4a1275fb58..1fdd37225e6 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 @@ -20,7 +20,8 @@ import { BitbucketProject, BitbucketRepository, - GithubRepository + GithubRepository, + GitlabProject } from '../../types/alm-integration'; export function mockBitbucketProject(overrides: Partial = {}): BitbucketProject { @@ -54,3 +55,16 @@ export function mockGitHubRepository(overrides: Partial = {}): ...overrides }; } + +export function mockGitlabProject(overrides: Partial = {}): GitlabProject { + return { + id: 'id1234', + name: 'Awesome Project !', + slug: 'awesome-project-exclamation', + pathName: 'Company / Best Projects', + pathSlug: 'company/best-projects', + sqProjectKey: '', + url: 'https://gitlab.company.com/best-projects/awesome-project-exclamation', + ...overrides + }; +} 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 4a2a5864e8c..cf70f73017a 100644 --- a/server/sonar-web/src/main/js/types/alm-integration.ts +++ b/server/sonar-web/src/main/js/types/alm-integration.ts @@ -48,3 +48,14 @@ export interface GithubRepository { url: string; sqProjectKey: string; } + +export interface GitlabProject { + id: string; + name: string; + pathName: string; + pathSlug: string; + sqProjectKey?: string; + sqProjectName?: string; + slug: string; + url: 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 dcf07e58c1a..7a1b7ee02a0 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3172,6 +3172,9 @@ onboarding.create_project.github.warning.message_admin.link=ALM integration sett onboarding.create_project.github.no_orgs=We couldn't load any organizations with your key. Contact an administrator. onboarding.create_project.github.no_orgs_admin=We couldn't load any organizations. Make sure the GitHub App is installed in at least one organization and check the GitHub instance configuration in the {link}. onboarding.create_project.gitlab.title=Which GitLab project do you want to setup? +onboarding.create_project.gitlab.no_projects=No projects could be fetched from Gitlab. Contact your system administrator, or {link}. +onboarding.create_project.gitlab.link=See on GitLab +onboarding.create_project.gitlab.search_prompt=Search for projects onboarding.create_organization.page.header=Create Organization onboarding.create_organization.page.description=An organization is a space where a team or a whole company can collaborate accross many projects. -- 2.39.5