123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 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 React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
- import { getGithubOrganizations, getGithubRepositories } from '../../../../api/alm-integrations';
- import { useLocation, useRouter } from '../../../../components/hoc/withRouter';
- import { LabelValueSelectOption } from '../../../../helpers/search';
- import { GithubOrganization, GithubRepository } from '../../../../types/alm-integration';
- import { AlmSettingsInstance } from '../../../../types/alm-settings';
- import { DopSetting } from '../../../../types/dop-translation';
- import { Paging } from '../../../../types/types';
- import { ImportProjectParam } from '../CreateProjectPage';
- import MonorepoProjectCreate from '../monorepo/MonorepoProjectCreate';
- import { CreateProjectModes } from '../types';
- import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer';
- import { redirectToGithub } from './utils';
-
- interface Props {
- canAdmin: boolean;
- isLoadingBindings: boolean;
- onProjectSetupDone: (importProjects: ImportProjectParam) => void;
- dopSettings: DopSetting[];
- }
-
- const REPOSITORY_PAGE_SIZE = 50;
- const REPOSITORY_SEARCH_DEBOUNCE_TIME = 250;
-
- export default function GitHubProjectCreate(props: Readonly<Props>) {
- const { canAdmin, dopSettings, isLoadingBindings, onProjectSetupDone } = props;
-
- const repositorySearchDebounceId = useRef<NodeJS.Timeout | undefined>();
-
- const [isInError, setIsInError] = useState(false);
- const [isLoadingOrganizations, setIsLoadingOrganizations] = useState(true);
- const [isLoadingRepositories, setIsLoadingRepositories] = useState(false);
- const [organizations, setOrganizations] = useState<GithubOrganization[]>([]);
- const [repositories, setRepositories] = useState<GithubRepository[]>([]);
- const [repositoryPaging, setRepositoryPaging] = useState<Paging>({
- pageSize: REPOSITORY_PAGE_SIZE,
- total: 0,
- pageIndex: 1,
- });
- const [searchQuery, setSearchQuery] = useState('');
- const [selectedDopSetting, setSelectedDopSetting] = useState<DopSetting>();
- const [selectedOrganization, setSelectedOrganization] = useState<GithubOrganization>();
- const [selectedRepository, setSelectedRepository] = useState<GithubRepository>();
-
- const location = useLocation();
- const router = useRouter();
-
- const isMonorepoSetup = location.query?.mono === 'true';
- const hasDopSettings = Boolean(dopSettings?.length);
- const organizationOptions = useMemo(() => {
- return organizations.map(transformToOption);
- }, [organizations]);
- const repositoryOptions = useMemo(() => {
- return repositories.map(transformToOption);
- }, [repositories]);
-
- const fetchRepositories = useCallback(
- async (params: { organizationKey: string; page?: number; query?: string }) => {
- const { organizationKey, page = 1, query } = params;
-
- if (selectedDopSetting === undefined) {
- setIsInError(true);
- return;
- }
-
- setIsLoadingRepositories(true);
-
- try {
- const { paging, repositories } = await getGithubRepositories({
- almSetting: selectedDopSetting.key,
- organization: organizationKey,
- pageSize: REPOSITORY_PAGE_SIZE,
- page,
- query,
- });
-
- setRepositoryPaging(paging);
- setRepositories((prevRepositories) =>
- page === 1 ? repositories : [...prevRepositories, ...repositories],
- );
- } catch (_) {
- setRepositoryPaging({ pageIndex: 1, pageSize: REPOSITORY_PAGE_SIZE, total: 0 });
- setRepositories([]);
- } finally {
- setIsLoadingRepositories(false);
- }
- },
- [selectedDopSetting],
- );
-
- const handleImportRepository = useCallback(
- (repoKeys: string[]) => {
- if (selectedDopSetting && selectedOrganization && repoKeys.length > 0) {
- onProjectSetupDone({
- almSetting: selectedDopSetting.key,
- creationMode: CreateProjectModes.GitHub,
- monorepo: false,
- projects: repoKeys.map((repositoryKey) => ({ repositoryKey })),
- });
- }
- },
- [onProjectSetupDone, selectedDopSetting, selectedOrganization],
- );
-
- const handleLoadMore = useCallback(() => {
- if (selectedOrganization) {
- fetchRepositories({
- organizationKey: selectedOrganization.key,
- page: repositoryPaging.pageIndex + 1,
- query: searchQuery,
- });
- }
- }, [fetchRepositories, repositoryPaging.pageIndex, searchQuery, selectedOrganization]);
-
- const handleSelectOrganization = useCallback(
- (organizationKey: string) => {
- setSearchQuery('');
- setSelectedOrganization(organizations.find(({ key }) => key === organizationKey));
- fetchRepositories({ organizationKey });
- },
- [fetchRepositories, organizations],
- );
-
- const handleSelectRepository = useCallback(
- (repositoryIdentifier: string) => {
- setSelectedRepository(repositories.find(({ key }) => key === repositoryIdentifier));
- },
- [repositories],
- );
-
- const authenticateToGithub = useCallback(async () => {
- try {
- await redirectToGithub({ isMonorepoSetup, selectedDopSetting });
- } catch {
- setIsInError(true);
- }
- }, [isMonorepoSetup, selectedDopSetting]);
-
- const onSelectDopSetting = useCallback((setting: DopSetting | undefined) => {
- setSelectedDopSetting(setting);
- setOrganizations([]);
- setRepositories([]);
- setSearchQuery('');
- }, []);
-
- const onSelectedAlmInstanceChange = useCallback(
- (instance: AlmSettingsInstance) => {
- onSelectDopSetting(dopSettings.find((dopSetting) => dopSetting.key === instance.key));
- },
- [dopSettings, onSelectDopSetting],
- );
-
- useEffect(() => {
- const selectedDopSettingId = location.query?.dopSetting;
- if (selectedDopSettingId !== undefined) {
- const selectedDopSetting = dopSettings.find(({ id }) => id === selectedDopSettingId);
-
- if (selectedDopSetting) {
- setSelectedDopSetting(selectedDopSetting);
- }
-
- return;
- }
-
- if (dopSettings.length > 1) {
- setSelectedDopSetting(undefined);
- return;
- }
-
- setSelectedDopSetting(dopSettings[0]);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [hasDopSettings]);
-
- useEffect(() => {
- if (selectedDopSetting?.url === undefined) {
- setIsInError(true);
- return;
- }
- setIsInError(false);
-
- const code = location.query?.code;
- if (code === undefined) {
- authenticateToGithub().catch(() => {
- setIsInError(true);
- });
- } else {
- delete location.query.code;
- router.replace(location);
-
- getGithubOrganizations(selectedDopSetting.key, code)
- .then(({ organizations }) => {
- setOrganizations(organizations);
- setIsLoadingOrganizations(false);
- })
- .catch(() => {
- setIsInError(true);
- });
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectedDopSetting]);
-
- useEffect(() => {
- repositorySearchDebounceId.current = setTimeout(() => {
- if (selectedOrganization) {
- fetchRepositories({
- organizationKey: selectedOrganization.key,
- query: searchQuery,
- });
- }
- }, REPOSITORY_SEARCH_DEBOUNCE_TIME);
-
- return () => {
- clearTimeout(repositorySearchDebounceId.current);
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [searchQuery]);
-
- return isMonorepoSetup ? (
- <MonorepoProjectCreate
- dopSettings={dopSettings}
- canAdmin={canAdmin}
- error={isInError}
- loadingBindings={isLoadingBindings}
- loadingOrganizations={isLoadingOrganizations}
- loadingRepositories={isLoadingRepositories}
- onProjectSetupDone={onProjectSetupDone}
- onSearchRepositories={setSearchQuery}
- onSelectDopSetting={onSelectDopSetting}
- onSelectOrganization={handleSelectOrganization}
- onSelectRepository={handleSelectRepository}
- organizationOptions={organizationOptions}
- repositoryOptions={repositoryOptions}
- repositorySearchQuery={searchQuery}
- selectedDopSetting={selectedDopSetting}
- selectedOrganization={selectedOrganization && transformToOption(selectedOrganization)}
- selectedRepository={selectedRepository && transformToOption(selectedRepository)}
- />
- ) : (
- <GitHubProjectCreateRenderer
- almInstances={dopSettings.map(({ key, type, url }) => ({
- alm: type,
- key,
- url,
- }))}
- canAdmin={canAdmin}
- error={isInError}
- loadingBindings={isLoadingBindings}
- loadingOrganizations={isLoadingOrganizations}
- loadingRepositories={isLoadingRepositories}
- onImportRepository={handleImportRepository}
- onLoadMore={handleLoadMore}
- onSearch={setSearchQuery}
- onSelectedAlmInstanceChange={onSelectedAlmInstanceChange}
- onSelectOrganization={handleSelectOrganization}
- organizations={organizations}
- repositories={repositories}
- repositoryPaging={repositoryPaging}
- searchQuery={searchQuery}
- selectedAlmInstance={
- selectedDopSetting && {
- alm: selectedDopSetting.type,
- key: selectedDopSetting.key,
- url: selectedDopSetting.url,
- }
- }
- selectedOrganization={selectedOrganization}
- />
- );
- }
-
- function transformToOption({
- key,
- name,
- }: GithubOrganization | GithubRepository): LabelValueSelectOption {
- return { value: key, label: name };
- }
|