您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

GitHubProjectCreateRenderer.tsx 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. /* eslint-disable react/no-unused-prop-types */
  21. import { Link, Spinner } from '@sonarsource/echoes-react';
  22. import { DarkLabel, FlagMessage, InputSelect, LightPrimary, Title } from 'design-system';
  23. import React, { useContext, useEffect, useState } from 'react';
  24. import { FormattedMessage } from 'react-intl';
  25. import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext';
  26. import { translate } from '../../../../helpers/l10n';
  27. import { LabelValueSelectOption } from '../../../../helpers/search';
  28. import { queryToSearch } from '../../../../helpers/urls';
  29. import { GithubOrganization, GithubRepository } from '../../../../types/alm-integration';
  30. import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
  31. import { Feature } from '../../../../types/features';
  32. import { Paging } from '../../../../types/types';
  33. import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown';
  34. import RepositoryList from '../components/RepositoryList';
  35. import { CreateProjectModes } from '../types';
  36. interface GitHubProjectCreateRendererProps {
  37. canAdmin: boolean;
  38. error: boolean;
  39. loadingBindings: boolean;
  40. loadingOrganizations: boolean;
  41. loadingRepositories: boolean;
  42. onImportRepository: (key: string[]) => void;
  43. onLoadMore: () => void;
  44. onSearch: (q: string) => void;
  45. onSelectOrganization: (key: string) => void;
  46. organizations: GithubOrganization[];
  47. repositories?: GithubRepository[];
  48. repositoryPaging: Paging;
  49. searchQuery: string;
  50. selectedOrganization?: GithubOrganization;
  51. almInstances: AlmSettingsInstance[];
  52. selectedAlmInstance?: AlmSettingsInstance;
  53. onSelectedAlmInstanceChange: (instance: AlmSettingsInstance) => void;
  54. }
  55. function orgToOption({ key, name }: GithubOrganization) {
  56. return { value: key, label: name };
  57. }
  58. export default function GitHubProjectCreateRenderer(
  59. props: Readonly<GitHubProjectCreateRendererProps>,
  60. ) {
  61. const isMonorepoSupported = useContext(AvailableFeaturesContext).includes(
  62. Feature.MonoRepositoryPullRequestDecoration,
  63. );
  64. const {
  65. canAdmin,
  66. error,
  67. loadingBindings,
  68. loadingOrganizations,
  69. organizations,
  70. selectedOrganization,
  71. almInstances,
  72. selectedAlmInstance,
  73. repositories,
  74. } = props;
  75. const [selected, setSelected] = useState<Set<string>>(new Set());
  76. useEffect(() => {
  77. const selectedKeys = Array.from(selected).filter((key) =>
  78. repositories?.find((r) => r.key === key),
  79. );
  80. setSelected(new Set(selectedKeys));
  81. // We want to update only when `repositories` changes.
  82. // If we subscribe to `selected` changes we will enter an infinite loop.
  83. // eslint-disable-next-line react-hooks/exhaustive-deps
  84. }, [repositories]);
  85. if (loadingBindings) {
  86. return <Spinner />;
  87. }
  88. const handleCheck = (key: string) => {
  89. setSelected((prev) => new Set(prev.delete(key) ? prev : prev.add(key)));
  90. };
  91. const handleCheckAll = () => {
  92. setSelected(
  93. new Set(repositories?.filter((r) => r.sqProjectKey === undefined).map((r) => r.key) ?? []),
  94. );
  95. };
  96. const handleImport = () => {
  97. props.onImportRepository(Array.from(selected));
  98. };
  99. const handleUncheckAll = () => {
  100. setSelected(new Set());
  101. };
  102. return (
  103. <>
  104. <header className="sw-mb-10">
  105. <Title className="sw-mb-4">{translate('onboarding.create_project.github.title')}</Title>
  106. <LightPrimary className="sw-body-sm">
  107. {isMonorepoSupported ? (
  108. <FormattedMessage
  109. id="onboarding.create_project.github.subtitle.with_monorepo"
  110. values={{
  111. monorepoSetupLink: (
  112. <Link
  113. to={{
  114. pathname: '/projects/create',
  115. search: queryToSearch({
  116. mode: CreateProjectModes.GitHub,
  117. mono: true,
  118. }),
  119. }}
  120. >
  121. <FormattedMessage id="onboarding.create_project.subtitle_monorepo_setup_link" />
  122. </Link>
  123. ),
  124. }}
  125. />
  126. ) : (
  127. <FormattedMessage id="onboarding.create_project.github.subtitle" />
  128. )}
  129. </LightPrimary>
  130. </header>
  131. <AlmSettingsInstanceDropdown
  132. almKey={AlmKeys.GitHub}
  133. almInstances={almInstances}
  134. selectedAlmInstance={selectedAlmInstance}
  135. onChangeConfig={props.onSelectedAlmInstanceChange}
  136. />
  137. {error && selectedAlmInstance && (
  138. <FlagMessage variant="warning" className="sw-my-2">
  139. <span>
  140. {canAdmin ? (
  141. <FormattedMessage
  142. id="onboarding.create_project.github.warning.message_admin"
  143. defaultMessage={translate('onboarding.create_project.github.warning.message_admin')}
  144. values={{
  145. link: (
  146. <Link to="/admin/settings?category=almintegration">
  147. {translate('onboarding.create_project.github.warning.message_admin.link')}
  148. </Link>
  149. ),
  150. }}
  151. />
  152. ) : (
  153. translate('onboarding.create_project.github.warning.message')
  154. )}
  155. </span>
  156. </FlagMessage>
  157. )}
  158. <Spinner isLoading={loadingOrganizations && !error}>
  159. {!error && (
  160. <div className="sw-flex sw-flex-col">
  161. <DarkLabel htmlFor="github-choose-organization" className="sw-mb-2">
  162. {translate('onboarding.create_project.github.choose_organization')}
  163. </DarkLabel>
  164. {organizations.length > 0 ? (
  165. <InputSelect
  166. className="sw-w-7/12 sw-mb-9"
  167. size="full"
  168. isSearchable
  169. inputId="github-choose-organization"
  170. options={organizations.map(orgToOption)}
  171. onChange={({ value }: LabelValueSelectOption) => props.onSelectOrganization(value)}
  172. value={selectedOrganization ? orgToOption(selectedOrganization) : null}
  173. />
  174. ) : (
  175. !loadingOrganizations && (
  176. <FlagMessage variant="error" className="sw-mb-2">
  177. <span>
  178. {canAdmin ? (
  179. <FormattedMessage
  180. id="onboarding.create_project.github.no_orgs_admin"
  181. defaultMessage={translate('onboarding.create_project.github.no_orgs_admin')}
  182. values={{
  183. link: (
  184. <Link to="/admin/settings?category=almintegration">
  185. {translate(
  186. 'onboarding.create_project.github.warning.message_admin.link',
  187. )}
  188. </Link>
  189. ),
  190. }}
  191. />
  192. ) : (
  193. translate('onboarding.create_project.github.no_orgs')
  194. )}
  195. </span>
  196. </FlagMessage>
  197. )
  198. )}
  199. </div>
  200. )}
  201. {selectedOrganization && (
  202. <RepositoryList
  203. {...props}
  204. almKey={AlmKeys.GitHub}
  205. checkAll={handleCheckAll}
  206. onCheck={handleCheck}
  207. onImport={handleImport}
  208. selected={selected}
  209. uncheckAll={handleUncheckAll}
  210. />
  211. )}
  212. </Spinner>
  213. </>
  214. );
  215. }