import { get, getJSON, post, postJSON } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import {
+ AzureProject,
+ AzureRepository,
BitbucketProject,
BitbucketRepository,
GithubOrganization,
});
}
+export function getAzureProjects(almSetting: string): Promise<{ projects: AzureProject[] }> {
+ 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[] }> {
--- /dev/null
+/*
+ * 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 (
+ <BoxedGroupAccordion
+ className={classNames('big-spacer-bottom', {
+ open
+ })}
+ onClick={handleClick}
+ open={open}
+ title={<h3>{project.name}</h3>}>
+ {open && (
+ <DeferredSpinner loading={loading}>
+ {/* The extra loading guard is to prevent the flash of the Alert */}
+ {!loading && repositories.length === 0 ? (
+ <Alert variant="warning">
+ <FormattedMessage
+ defaultMessage={translate('onboarding.create_project.azure.no_repositories')}
+ id="onboarding.create_project.azure.no_repositories"
+ values={{
+ link: (
+ <Link
+ to={{
+ pathname: '/projects/create',
+ query: { mode: CreateProjectModes.AzureDevOps, resetPat: 1 }
+ }}>
+ {translate('onboarding.create_project.update_your_token')}
+ </Link>
+ )
+ }}
+ />
+ </Alert>
+ ) : (
+ <>
+ <div className="display-flex-wrap">
+ {limitedRepositories.map(repo => (
+ <div
+ className="abs-width-400 overflow-hidden spacer-top spacer-bottom"
+ key={repo.name}>
+ <strong className="text-ellipsis" title={repo.name}>
+ {repo.name}
+ </strong>
+ </div>
+ ))}
+ </div>
+ <ListFooter
+ count={limitedRepositories.length}
+ total={repositories.length}
+ loadMore={() => setPage(p => p + 1)}
+ />
+ </>
+ )}
+ </DeferredSpinner>
+ )}
+ </BoxedGroupAccordion>
+ );
+}
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<WithRouterProps, 'location'> {
+interface Props extends Pick<WithRouterProps, 'location' | 'router'> {
canAdmin: boolean;
loadingBindings: boolean;
onProjectCreate: (projectKeys: string[]) => void;
interface State {
loading: boolean;
+ loadingRepositories: T.Dict<boolean>;
patIsValid?: boolean;
+ projects?: AzureProject[];
+ repositories: T.Dict<AzureRepository[]>;
settings?: AlmSettingsInstance;
submittingToken?: boolean;
tokenValidationFailed: boolean;
// one from the list.
settings: props.settings[0],
loading: false,
+ loadingRepositories: {},
+ repositories: {},
tokenValidationFailed: false
};
}
const patIsValid = await this.checkPersonalAccessToken().catch(() => 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<AzureProject[] | undefined> => {
+ const { settings } = this.state;
+
+ if (!settings) {
+ return Promise.resolve(undefined);
+ }
+
+ return getAzureProjects(settings.key).then(({ projects }) => projects);
+ };
+
+ fetchAzureRepositories = (projectKey: string): Promise<AzureRepository[]> => {
+ 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;
if (patIsValid) {
this.cleanUrl();
- await this.fetchInitialData();
+ this.fetchInitialData();
}
}
} catch (e) {
render() {
const { canAdmin, loadingBindings, location } = this.props;
- const { loading, patIsValid, settings, submittingToken, tokenValidationFailed } = this.state;
+ const {
+ loading,
+ loadingRepositories,
+ patIsValid,
+ projects,
+ repositories,
+ settings,
+ submittingToken,
+ tokenValidationFailed
+ } = this.state;
return (
<AzureCreateProjectRenderer
canAdmin={canAdmin}
loading={loading || loadingBindings}
+ loadingRepositories={loadingRepositories}
+ onOpenProject={this.handleOpenProject}
onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
+ projects={projects}
+ repositories={repositories}
settings={settings}
showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)}
submittingToken={submittingToken}
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
+import { AzureProject, AzureRepository } from '../../../types/alm-integration';
import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings';
import AzurePersonalAccessTokenForm from './AzurePersonalAccessTokenForm';
import AzureProjectsList from './AzureProjectsList';
export interface AzureProjectCreateRendererProps {
canAdmin?: boolean;
loading: boolean;
+ loadingRepositories: T.Dict<boolean>;
+ onOpenProject: (key: string) => void;
onPersonalAccessTokenCreate: (token: string) => void;
+ projects?: AzureProject[];
+ repositories: T.Dict<AzureRepository[]>;
settings?: AlmSettingsInstance;
showPersonalAccessTokenForm?: boolean;
submittingToken?: boolean;
const {
canAdmin,
loading,
+ loadingRepositories,
+ projects,
+ repositories,
showPersonalAccessTokenForm,
settings,
submittingToken,
/>
</div>
) : (
- <AzureProjectsList />
+ <AzureProjectsList
+ loadingRepositories={loadingRepositories}
+ onOpenProject={props.onOpenProject}
+ projects={projects}
+ repositories={repositories}
+ />
))}
</>
);
* 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<boolean>;
+ onOpenProject: (key: string) => void;
+ projects?: AzureProject[];
+ repositories: T.Dict<AzureRepository[]>;
+}
+
+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 (
+ <Alert className="spacer-top" variant="warning">
+ <FormattedMessage
+ defaultMessage={translate('onboarding.create_project.azure.no_projects')}
+ id="onboarding.create_project.azure.no_projects"
+ values={{
+ link: (
+ <Link
+ to={{
+ pathname: '/projects/create',
+ query: { mode: CreateProjectModes.AzureDevOps, resetPat: 1 }
+ }}>
+ {translate('onboarding.create_project.update_your_token')}
+ </Link>
+ )
+ }}
+ />
+ </Alert>
+ );
+ }
+
+ const filteredProjects = projects.slice(0, page * PAGE_SIZE);
-export default function AzureProjectsList(_props: AzureProjectsListProps) {
return (
<div>
- <Alert variant="warning">Coming soon!</Alert>
+ {filteredProjects.map((p, i) => (
+ <AzureProjectAccordion
+ key={p.key}
+ loading={Boolean(loadingRepositories[p.key])}
+ onOpen={props.onOpenProject}
+ project={p}
+ repositories={repositories[p.key]}
+ startsOpen={i === 0}
+ />
+ ))}
+
+ <ListFooter
+ count={filteredProjects.length}
+ loadMore={() => setPage(p => p + 1)}
+ total={projects.length}
+ />
</div>
);
}
loadingBindings={loading}
location={location}
onProjectCreate={this.handleProjectCreate}
+ router={router}
settings={azureSettings}
/>
);
--- /dev/null
+/*
+ * 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<AzureProjectAccordionProps> = {}) {
+ return shallow(
+ <AzureProjectAccordion
+ loading={false}
+ onOpen={jest.fn()}
+ project={mockAzureProject()}
+ startsOpen={true}
+ {...overrides}
+ />
+ );
+}
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: [] })
};
});
});
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);
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<AzureProjectCreate['props']> = {}) {
loadingBindings={false}
location={mockLocation()}
onProjectCreate={jest.fn()}
+ router={mockRouter()}
settings={[mockAlmSettingsInstance({ alm: AlmKeys.Azure, key: 'foo' })]}
{...overrides}
/>
/* 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, {
});
function shallowRender(overrides: Partial<AzureProjectCreateRendererProps>) {
+ const project = mockAzureProject();
+
return shallow(
<AzureProjectCreateRenderer
canAdmin={true}
loading={false}
+ loadingRepositories={{}}
+ onOpenProject={jest.fn()}
onPersonalAccessTokenCreate={jest.fn()}
+ projects={[project]}
+ repositories={{ [project.key]: [mockAzureRepository()] }}
tokenValidationFailed={false}
settings={mockAlmSettingsInstance({ alm: AlmKeys.Azure })}
showPersonalAccessTokenForm={false}
--- /dev/null
+/*
+ * 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 ListFooter from 'sonar-ui-common/components/controls/ListFooter';
+import { mockAzureProject } from '../../../../helpers/mocks/alm-integrations';
+import AzureProjectAccordion from '../AzureProjectAccordion';
+import AzureProjectsList, { AzureProjectsListProps } from '../AzureProjectsList';
+
+it('should render correctly', () => {
+ 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<AzureProjectsListProps> = {}) {
+ const project = mockAzureProject();
+
+ return shallow(
+ <AzureProjectsList
+ loadingRepositories={{}}
+ onOpenProject={jest.fn()}
+ projects={[project]}
+ repositories={{ [project.key]: [] }}
+ {...overrides}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: closed 1`] = `
+<BoxedGroupAccordion
+ className="big-spacer-bottom"
+ onClick={[Function]}
+ open={false}
+ title={
+ <h3>
+ Azure Project
+ </h3>
+ }
+/>
+`;
+
+exports[`should render correctly: loading 1`] = `
+<BoxedGroupAccordion
+ className="big-spacer-bottom open"
+ onClick={[Function]}
+ open={true}
+ title={
+ <h3>
+ Azure Project
+ </h3>
+ }
+>
+ <DeferredSpinner
+ loading={true}
+ >
+ <div
+ className="display-flex-wrap"
+ />
+ <ListFooter
+ count={0}
+ loadMore={[Function]}
+ total={0}
+ />
+ </DeferredSpinner>
+</BoxedGroupAccordion>
+`;
+
+exports[`should render correctly: with a repository 1`] = `
+<BoxedGroupAccordion
+ className="big-spacer-bottom open"
+ onClick={[Function]}
+ open={true}
+ title={
+ <h3>
+ Azure Project
+ </h3>
+ }
+>
+ <DeferredSpinner
+ loading={false}
+ >
+ <div
+ className="display-flex-wrap"
+ >
+ <div
+ className="abs-width-400 overflow-hidden spacer-top spacer-bottom"
+ key="Azure repo 1"
+ >
+ <strong
+ className="text-ellipsis"
+ title="Azure repo 1"
+ >
+ Azure repo 1
+ </strong>
+ </div>
+ </div>
+ <ListFooter
+ count={1}
+ loadMore={[Function]}
+ total={1}
+ />
+ </DeferredSpinner>
+</BoxedGroupAccordion>
+`;
<AzureProjectCreateRenderer
canAdmin={true}
loading={true}
+ loadingRepositories={Object {}}
+ onOpenProject={[Function]}
onPersonalAccessTokenCreate={[Function]}
+ repositories={Object {}}
settings={
Object {
"alm": "azure",
</span>
}
/>
- <AzureProjectsList />
+ <AzureProjectsList
+ loadingRepositories={Object {}}
+ onOpenProject={[MockFunction]}
+ projects={
+ Array [
+ Object {
+ "key": "azure-project-1",
+ "name": "Azure Project",
+ },
+ ]
+ }
+ repositories={
+ Object {
+ "azure-project-1": Array [
+ Object {
+ "name": "Azure repo 1",
+ "projectName": "Azure Project",
+ },
+ ],
+ }
+ }
+ />
</Fragment>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: default 1`] = `
+<div>
+ <AzureProjectAccordion
+ key="azure-project-1"
+ loading={false}
+ onOpen={[MockFunction]}
+ project={
+ Object {
+ "key": "azure-project-1",
+ "name": "Azure Project",
+ }
+ }
+ repositories={Array []}
+ startsOpen={true}
+ />
+ <ListFooter
+ count={1}
+ loadMore={[Function]}
+ total={1}
+ />
+</div>
+`;
+
+exports[`should render correctly: empty 1`] = `
+<Alert
+ className="spacer-top"
+ variant="warning"
+>
+ <FormattedMessage
+ defaultMessage="onboarding.create_project.azure.no_projects"
+ id="onboarding.create_project.azure.no_projects"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/projects/create",
+ "query": Object {
+ "mode": "azure",
+ "resetPat": 1,
+ },
+ }
+ }
+ >
+ onboarding.create_project.update_your_token
+ </Link>,
+ }
+ }
+ />
+</Alert>
+`;
}
}
onProjectCreate={[Function]}
+ router={
+ Object {
+ "createHref": [MockFunction],
+ "createPath": [MockFunction],
+ "go": [MockFunction],
+ "goBack": [MockFunction],
+ "goForward": [MockFunction],
+ "isActive": [MockFunction],
+ "push": [MockFunction],
+ "replace": [MockFunction],
+ "setRouteLeaveHook": [MockFunction],
+ }
+ }
settings={Array []}
/>
</div>
settings.almintegration.form.url.azure.help
<br />
<em>
- https://ado.your-company.com/
+ https://ado.your-company.com/DefaultCollection
</em>
</React.Fragment>
}
settings.almintegration.form.url.azure.help
<br />
<em>
- https://ado.your-company.com/
+ https://ado.your-company.com/DefaultCollection
</em>
</React.Fragment>
}
* 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> = {}): AzureProject {
+ return {
+ key: 'azure-project-1',
+ name: 'Azure Project',
+ ...overrides
+ };
+}
+
+export function mockAzureRepository(overrides: Partial<AzureRepository> = {}): AzureRepository {
+ return {
+ name: 'Azure repo 1',
+ projectName: 'Azure Project',
+ ...overrides
+ };
+}
+
export function mockBitbucketProject(overrides: Partial<BitbucketProject> = {}): BitbucketProject {
return {
id: 1,
* 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;
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