]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20086 Migrate Azure import page to the new UI
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 9 Aug 2023 13:16:13 +0000 (15:16 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 14 Aug 2023 20:02:57 +0000 (20:02 +0000)
server/sonar-web/design-system/src/components/Accordion.tsx
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreateRenderer.tsx
server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx
server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx
server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx
server/sonar-web/src/main/js/apps/create/project/style.css [deleted file]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b6a9770898881c2d15c87614daa70f5fa275ecff..22c49a3b362d7f473bf5d7586eee8b1825213c02 100644 (file)
@@ -48,7 +48,7 @@ export function Accordion(props: AccordionProps) {
 
   return (
     <Container
-      className={classNames('sw-cursor-pointer', className, {
+      className={classNames(className, {
         open,
       })}
       role="listitem"
@@ -85,7 +85,6 @@ const accordionStyle = (props: ThemedProps) => css`
   }
   ${tw`sw-rounded-2`}
   ${tw`sw-overflow-hidden`}
-  ${tw`sw-cursor-pointer`}
 
   & > button:hover, & > button:active {
     color: ${themeContrast('buttonSecondary')(props)};
index 8d58b630c2cd38c15a0e7c217586d0852dc49d8b..1cb9fb701d4f4a0f6248146b669b81ef3519c831 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import classNames from 'classnames';
+import { Accordion, DeferredSpinner, FlagMessage, Link, SearchHighlighter } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { colors } from '../../../../app/theme';
-import Link from '../../../../components/common/Link';
-import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion';
 import ListFooter from '../../../../components/controls/ListFooter';
-import Radio from '../../../../components/controls/Radio';
-import CheckIcon from '../../../../components/icons/CheckIcon';
-import { Alert } from '../../../../components/ui/Alert';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
 import { translate } from '../../../../helpers/l10n';
-import { getProjectUrl, queryToSearch } from '../../../../helpers/urls';
+import { getBaseUrl } from '../../../../helpers/system';
+import { queryToSearch } from '../../../../helpers/urls';
 import { AzureProject, AzureRepository } from '../../../../types/alm-integration';
+import AlmRepoItem from '../components/AlmRepoItem';
 import { CreateProjectModes } from '../types';
 
 export interface AzureProjectAccordionProps {
   loading: boolean;
   onOpen: (key: string) => void;
-  onSelectRepository: (repository: AzureRepository) => void;
+  onImportRepository: (repository: AzureRepository) => void;
   project: AzureProject;
   repositories?: AzureRepository[];
   searchQuery?: string;
-  selectedRepository?: AzureRepository;
   startsOpen: boolean;
 }
 
-const PAGE_SIZE = 30;
-
-function highlight(text: string, term?: string, underline = false) {
-  if (!term || !text.toLowerCase().includes(term.toLowerCase())) {
-    return text;
-  }
-
-  // Capture only the first occurence by using a capturing group to get
-  // everything after the first occurence
-  const [pre, found, post] = text.split(new RegExp(`(${term})(.*)`, 'i'));
-  return (
-    <>
-      {pre}
-      <strong className={classNames({ underline })}>{found}</strong>
-      {post}
-    </>
-  );
-}
+const PAGE_SIZE = 20;
 
 export default function AzureProjectAccordion(props: AzureProjectAccordionProps) {
-  const {
-    loading,
-    startsOpen,
-    project,
-    repositories = [],
-    searchQuery,
-    selectedRepository,
-  } = props;
+  const { loading, startsOpen, project, repositories = [], searchQuery } = props;
 
   const [open, setOpen] = React.useState(startsOpen);
   const handleClick = () => {
@@ -84,23 +54,22 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps)
   const [page, setPage] = React.useState(1);
   const limitedRepositories = repositories.slice(0, page * PAGE_SIZE);
 
-  const isSelected = (repo: AzureRepository) =>
-    selectedRepository?.projectName === project.name && selectedRepository.name === repo.name;
-
   return (
-    <BoxedGroupAccordion
-      className={classNames('big-spacer-bottom', {
-        open,
-      })}
+    <Accordion
       onClick={handleClick}
       open={open}
-      title={<h3 title={project.description}>{highlight(project.name, searchQuery, true)}</h3>}
+      header={
+        <span title={project.description}>
+          <SearchHighlighter term={searchQuery}>{project.name}</SearchHighlighter>
+        </span>
+      }
     >
+      {/* eslint-disable-next-line local-rules/no-conditional-rendering-of-deferredspinner*/}
       {open && (
         <DeferredSpinner loading={loading}>
           {/* The extra loading guard is to prevent the flash of the Alert */}
           {!loading && repositories.length === 0 ? (
-            <Alert variant="warning">
+            <FlagMessage variant="warning">
               <FormattedMessage
                 defaultMessage={translate('onboarding.create_project.azure.no_repositories')}
                 id="onboarding.create_project.azure.no_repositories"
@@ -120,50 +89,35 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps)
                   ),
                 }}
               />
-            </Alert>
+            </FlagMessage>
           ) : (
             <>
-              <div className="display-flex-wrap">
-                {limitedRepositories.map((repo) => (
-                  <div
-                    className="create-project-azdo-repo display-flex-start spacer-bottom padded-right"
-                    key={repo.name}
-                  >
-                    {repo.sqProjectKey ? (
-                      <>
-                        <CheckIcon className="spacer-right" fill={colors.green} size={14} />
-                        <div className="overflow-hidden">
-                          <div className="little-spacer-bottom text-ellipsis">
-                            <Link to={getProjectUrl(repo.sqProjectKey)} title={repo.sqProjectName}>
-                              {highlight(repo.sqProjectName || repo.name, searchQuery)}
-                            </Link>
-                          </div>
-                          <em>{translate('onboarding.create_project.repository_imported')}</em>
-                        </div>
-                      </>
-                    ) : (
-                      <Radio
-                        checked={isSelected(repo)}
-                        className="overflow-hidden"
-                        alignLabel
-                        onCheck={() => props.onSelectRepository(repo)}
-                        value={repo.name}
-                      >
-                        <span title={repo.name}>{highlight(repo.name, searchQuery)}</span>
-                      </Radio>
-                    )}
-                  </div>
+              <div className="sw-flex sw-flex-col sw-gap-3">
+                {limitedRepositories.map((r) => (
+                  <AlmRepoItem
+                    key={r.name}
+                    almKey={r.name}
+                    almIconSrc={`${getBaseUrl()}/images/alm/azure.svg`}
+                    sqProjectKey={r.sqProjectKey}
+                    onImport={() => props.onImportRepository(r)}
+                    primaryTextNode={
+                      <span title={r.name}>
+                        <SearchHighlighter term={searchQuery}>{r.name}</SearchHighlighter>
+                      </span>
+                    }
+                  />
                 ))}
               </div>
               <ListFooter
                 count={limitedRepositories.length}
                 total={repositories.length}
                 loadMore={() => setPage((p) => p + 1)}
+                useMIUIButtons
               />
             </>
           )}
         </DeferredSpinner>
       )}
-    </BoxedGroupAccordion>
+    </Accordion>
   );
 }
index ef64815293cdf352c4a99046b0ddb98d4dc23c52..3dfad9a6ed9909883e0d45f34609f2e95045976e 100644 (file)
@@ -52,7 +52,6 @@ interface State {
   searching?: boolean;
   searchResults?: AzureRepository[];
   searchQuery?: string;
-  selectedRepository?: AzureRepository;
   selectedAlmInstance?: AlmSettingsInstance;
   submittingToken?: boolean;
   tokenValidationFailed: boolean;
@@ -211,8 +210,8 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
     }
   };
 
-  handleImportRepository = () => {
-    const { selectedRepository, selectedAlmInstance } = this.state;
+  handleImportRepository = (selectedRepository: AzureRepository) => {
+    const { selectedAlmInstance } = this.state;
 
     if (selectedAlmInstance && selectedRepository) {
       this.props.onProjectSetupDone(
@@ -225,10 +224,6 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
     }
   };
 
-  handleSelectRepository = (selectedRepository: AzureRepository) => {
-    this.setState({ selectedRepository });
-  };
-
   checkPersonalAccessToken = () => {
     const { selectedAlmInstance } = this.state;
 
@@ -295,7 +290,6 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
       searching,
       searchResults,
       searchQuery,
-      selectedRepository,
       selectedAlmInstance,
       submittingToken,
       tokenValidationFailed,
@@ -311,13 +305,11 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
         onOpenProject={this.handleOpenProject}
         onPersonalAccessTokenCreate={this.handlePersonalAccessTokenCreate}
         onSearch={this.handleSearchRepositories}
-        onSelectRepository={this.handleSelectRepository}
         projects={projects}
         repositories={repositories}
         searching={searching}
         searchResults={searchResults}
         searchQuery={searchQuery}
-        selectedRepository={selectedRepository}
         almInstances={almInstances}
         selectedAlmInstance={selectedAlmInstance}
         showPersonalAccessTokenForm={!patIsValid || Boolean(location.query.resetPat)}
index 449961cb081af79ea5a45d6f8407912cd5a8a29a..a6e4fb2ef26a16c008c8f40aedd958857a705658 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import {
+  DeferredSpinner,
+  FlagMessage,
+  InputSearch,
+  LightPrimary,
+  Link,
+  PageContentFontWrapper,
+  Title,
+} from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import Link from '../../../../components/common/Link';
-import SearchBox from '../../../../components/controls/SearchBox';
-import { Button } from '../../../../components/controls/buttons';
-import { Alert } from '../../../../components/ui/Alert';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
 import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
 import { getGlobalSettingsUrl } from '../../../../helpers/urls';
 import { AzureProject, AzureRepository } from '../../../../types/alm-integration';
 import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
 import { Dict } from '../../../../types/types';
 import { ALM_INTEGRATION_CATEGORY } from '../../../settings/constants';
 import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown';
-import CreateProjectPageHeader from '../components/CreateProjectPageHeader';
 import WrongBindingCountAlert from '../components/WrongBindingCountAlert';
 import AzurePersonalAccessTokenForm from './AzurePersonalAccessTokenForm';
 import AzureProjectsList from './AzureProjectsList';
@@ -41,17 +43,15 @@ export interface AzureProjectCreateRendererProps {
   canAdmin?: boolean;
   loading: boolean;
   loadingRepositories: Dict<boolean>;
-  onImportRepository: () => void;
+  onImportRepository: (resository: AzureRepository) => void;
   onOpenProject: (key: string) => void;
   onPersonalAccessTokenCreate: (token: string) => void;
   onSearch: (query: string) => void;
-  onSelectRepository: (repository: AzureRepository) => void;
   projects?: AzureProject[];
   repositories: Dict<AzureRepository[]>;
   searching?: boolean;
   searchResults?: AzureRepository[];
   searchQuery?: string;
-  selectedRepository?: AzureRepository;
   almInstances?: AlmSettingsInstance[];
   selectedAlmInstance?: AlmSettingsInstance;
   showPersonalAccessTokenForm?: boolean;
@@ -71,7 +71,6 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
     searching,
     searchResults,
     searchQuery,
-    selectedRepository,
     almInstances,
     showPersonalAccessTokenForm,
     submittingToken,
@@ -81,38 +80,16 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
   } = props;
 
   const showCountError = !loading && (!almInstances || almInstances?.length === 0);
-  const settingIsValid = selectedAlmInstance && selectedAlmInstance.url;
   const showUrlError = !loading && selectedAlmInstance && !selectedAlmInstance.url;
 
   return (
-    <>
-      <CreateProjectPageHeader
-        additionalActions={
-          !showPersonalAccessTokenForm &&
-          settingIsValid && (
-            <div className="display-flex-center pull-right">
-              <Button
-                className="button-large button-primary"
-                disabled={!selectedRepository}
-                onClick={props.onImportRepository}
-              >
-                {translate('onboarding.create_project.import_selected_repo')}
-              </Button>
-            </div>
-          )
-        }
-        title={
-          <span className="text-middle">
-            <img
-              alt="" // Should be ignored by screen readers
-              className="spacer-right"
-              height="24"
-              src={`${getBaseUrl()}/images/alm/azure.svg`}
-            />
-            {translate('onboarding.create_project.azure.title')}
-          </span>
-        }
-      />
+    <PageContentFontWrapper>
+      <header className="sw-mb-10">
+        <Title className="sw-mb-4">{translate('onboarding.create_project.azure.title')}</Title>
+        <LightPrimary className="sw-body-sm">
+          {translate('onboarding.create_project.azure.subtitle')}
+        </LightPrimary>
+      </header>
 
       <AlmSettingsInstanceDropdown
         almKey={AlmKeys.Azure}
@@ -121,10 +98,10 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
         onChangeConfig={props.onSelectedAlmInstanceChange}
       />
 
-      {loading && <i className="spinner" />}
+      <DeferredSpinner loading={loading} />
 
       {showUrlError && (
-        <Alert variant="error">
+        <FlagMessage variant="error">
           {canAdmin ? (
             <FormattedMessage
               defaultMessage={translate('onboarding.create_project.azure.no_url.admin')}
@@ -141,7 +118,7 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
           ) : (
             translate('onboarding.create_project.azure.no_url')
           )}
-        </Alert>
+        </FlagMessage>
       )}
 
       {showCountError && <WrongBindingCountAlert alm={AlmKeys.Azure} canAdmin={!!canAdmin} />}
@@ -161,26 +138,27 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend
           </div>
         ) : (
           <>
-            <div className="huge-spacer-bottom">
-              <SearchBox
+            <div className="sw-mb-10 sw-w-abs-400">
+              <InputSearch
+                clearIconAriaLabel={translate('clear')}
                 onChange={props.onSearch}
                 placeholder={translate('onboarding.create_project.search_projects_repositories')}
+                size="full"
               />
             </div>
             <DeferredSpinner loading={Boolean(searching)}>
               <AzureProjectsList
                 loadingRepositories={loadingRepositories}
                 onOpenProject={props.onOpenProject}
-                onSelectRepository={props.onSelectRepository}
+                onImportRepository={props.onImportRepository}
                 projects={projects}
                 repositories={repositories}
                 searchResults={searchResults}
                 searchQuery={searchQuery}
-                selectedRepository={selectedRepository}
               />
             </DeferredSpinner>
           </>
         ))}
-    </>
+    </PageContentFontWrapper>
   );
 }
index 87f77c4edd35b22b4a47f0e3e43591f86adc63f9..e4caacd0a6c32db6fea925f2a50d784677c74573 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { FlagMessage, Link } from 'design-system';
 import { uniqBy } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import Link from '../../../../components/common/Link';
 import ListFooter from '../../../../components/controls/ListFooter';
-import { Alert } from '../../../../components/ui/Alert';
 import { translate, translateWithParameters } from '../../../../helpers/l10n';
 import { queryToSearch } from '../../../../helpers/urls';
 import { AzureProject, AzureRepository } from '../../../../types/alm-integration';
@@ -33,39 +32,31 @@ import AzureProjectAccordion from './AzureProjectAccordion';
 export interface AzureProjectsListProps {
   loadingRepositories: Dict<boolean>;
   onOpenProject: (key: string) => void;
-  onSelectRepository: (repository: AzureRepository) => void;
+  onImportRepository: (repository: AzureRepository) => void;
   projects?: AzureProject[];
   repositories: Dict<AzureRepository[]>;
   searchResults?: AzureRepository[];
   searchQuery?: string;
-  selectedRepository?: AzureRepository;
 }
 
 const PAGE_SIZE = 10;
 
 export default function AzureProjectsList(props: AzureProjectsListProps) {
-  const {
-    loadingRepositories,
-    projects = [],
-    repositories,
-    searchResults,
-    searchQuery,
-    selectedRepository,
-  } = props;
+  const { loadingRepositories, projects = [], repositories, searchResults, searchQuery } = props;
 
   const [page, setPage] = React.useState(1);
 
   if (searchResults && searchResults.length === 0) {
     return (
-      <Alert className="spacer-top" variant="warning">
+      <FlagMessage className="sw-mt-2" variant="warning">
         {translate('onboarding.create_project.azure.no_results')}
-      </Alert>
+      </FlagMessage>
     );
   }
 
   if (projects.length === 0) {
     return (
-      <Alert className="spacer-top" variant="warning">
+      <FlagMessage className="sw-mt-2" variant="warning">
         <FormattedMessage
           defaultMessage={translate('onboarding.create_project.azure.no_projects')}
           id="onboarding.create_project.azure.no_projects"
@@ -82,7 +73,7 @@ export default function AzureProjectsList(props: AzureProjectsListProps) {
             ),
           }}
         />
-      </Alert>
+      </FlagMessage>
     );
   }
 
@@ -114,28 +105,31 @@ export default function AzureProjectsList(props: AzureProjectsListProps) {
 
   return (
     <div>
-      {displayedProjects.map((p, i) => (
-        <AzureProjectAccordion
-          key={`${p.name}${keySuffix}`}
-          loading={Boolean(loadingRepositories[p.name])}
-          onOpen={props.onOpenProject}
-          onSelectRepository={props.onSelectRepository}
-          project={p}
-          repositories={
-            searchResults
-              ? searchResults.filter((s) => s.projectName === p.name)
-              : repositories[p.name]
-          }
-          selectedRepository={selectedRepository}
-          searchQuery={searchQuery}
-          startsOpen={searchResults !== undefined || i === 0}
-        />
-      ))}
+      <div className="sw-flex sw-flex-col sw-gap-6">
+        {displayedProjects.map((p, i) => (
+          <AzureProjectAccordion
+            key={`${p.name}${keySuffix}`}
+            loading={Boolean(loadingRepositories[p.name])}
+            onOpen={props.onOpenProject}
+            onImportRepository={props.onImportRepository}
+            project={p}
+            repositories={
+              searchResults
+                ? searchResults.filter((s) => s.projectName === p.name)
+                : repositories[p.name]
+            }
+            searchQuery={searchQuery}
+            startsOpen={searchResults !== undefined || i === 0}
+          />
+        ))}
+      </div>
 
       <ListFooter
+        className="sw-mb-12"
         count={displayedProjects.length}
         loadMore={() => setPage((p) => p + 1)}
         total={filteredProjects.length}
+        useMIUIButtons
       />
     </div>
   );
index 664bc914ae6d302258e0f018f326d78834a4a2e7..d5122cd8003f297f4ab051b6eb27481864dfeba4 100644 (file)
@@ -41,7 +41,6 @@ import GitHubProjectCreate from './Github/GitHubProjectCreate';
 import GitlabProjectCreate from './Gitlab/GitlabProjectCreate';
 import NewCodeDefinitionSelection from './components/NewCodeDefinitionSelection';
 import ManualProjectCreate from './manual/ManualProjectCreate';
-import './style.css';
 import { CreateProjectApiCallback, CreateProjectModes } from './types';
 
 export interface CreateProjectPageProps extends WithAvailableFeaturesProps {
index 44672b96a720871618084e43f1f730207c37e1cf..bcd723a7e0207b124523c8475cfec853b9b177be 100644 (file)
@@ -89,18 +89,20 @@ function renderRepositoryList(props: GitHubProjectCreateRendererProps) {
             <LightPrimary className="sw-body-sm">{translate('no_results')}</LightPrimary>
           </div>
         ) : (
-          repositories.map((r) => (
-            <AlmRepoItem
-              key={r.key}
-              almKey={r.key}
-              almUrl={r.url}
-              almUrlText={translate('onboarding.create_project.see_on_github')}
-              almIconSrc={`${getBaseUrl()}/images/tutorials/github-actions.svg`}
-              sqProjectKey={r.sqProjectKey}
-              onImport={props.onImportRepository}
-              primaryTextNode={<span title={r.name}>{r.name}</span>}
-            />
-          ))
+          <div className="sw-flex sw-flex-col sw-gap-3">
+            {repositories.map((r) => (
+              <AlmRepoItem
+                key={r.key}
+                almKey={r.key}
+                almUrl={r.url}
+                almUrlText={translate('onboarding.create_project.see_on_github')}
+                almIconSrc={`${getBaseUrl()}/images/tutorials/github-actions.svg`}
+                sqProjectKey={r.sqProjectKey}
+                onImport={props.onImportRepository}
+                primaryTextNode={<span title={r.name}>{r.name}</span>}
+              />
+            ))}
+          </div>
         )}
 
         <div className="display-flex-justify-center width-100">
index 33c29f898515cc8dafbb9a5fb4a7e0d3c45dceeb..3460c7656d581af0c2b85a5ba7840b2ec5429235 100644 (file)
@@ -101,14 +101,22 @@ it('should show import project feature when PAT is already set', async () => {
     await selectEvent.select(ui.instanceSelector.get(), [/conf-azure-2/]);
   });
 
+  expect(screen.getByText('Azure project')).toBeInTheDocument();
   expect(screen.getByText('Azure project 2')).toBeInTheDocument();
-  const importButton = screen.getByText('onboarding.create_project.import_selected_repo');
-  const radioButton = screen.getByRole('radio', { name: 'Azure repo 2' });
 
-  expect(radioButton).toBeInTheDocument();
-  expect(importButton).toBeDisabled();
-  await user.click(radioButton);
-  expect(importButton).toBeEnabled();
+  expect(
+    screen.getByRole('row', {
+      name: 'Azure repo 1 onboarding.create_project.repository_imported',
+    })
+  ).toBeInTheDocument();
+
+  expect(
+    screen.getByRole('row', {
+      name: 'Azure repo 2 onboarding.create_project.import',
+    })
+  ).toBeInTheDocument();
+
+  const importButton = screen.getByText('onboarding.create_project.import');
   await user.click(importButton);
 
   expect(
index 08f0a44a77dfa09b5a2cae1b5195d25b8c0a0e5f..af61167b9d21e6979eb58805da432d6c18da7b64 100644 (file)
@@ -39,8 +39,8 @@ interface AlmRepoItemProps {
   secondaryTextNode?: React.ReactNode;
   sqProjectKey?: string;
   almKey: string;
-  almUrl: string;
-  almUrlText: string;
+  almUrl?: string;
+  almUrlText?: string;
   onImport: (key: string) => void;
   almIconSrc: string;
 }
@@ -59,12 +59,12 @@ export default function AlmRepoItem({
     <StyledCard
       key={almKey}
       role="row"
-      className={classNames('sw-flex sw-mb-2 sw-px-4', {
+      className={classNames('sw-flex sw-px-4', {
         'sw-py-4': sqProjectKey !== undefined,
         'sw-py-2': sqProjectKey === undefined,
       })}
     >
-      <div className="sw-w-[70%] sw-flex sw-mr-1">
+      <div className="sw-w-[70%] sw-min-w-0 sw-flex sw-mr-1">
         <div className="sw-max-w-[50%] sw-flex sw-items-center">
           <img
             alt="" // Should be ignored by screen readers
@@ -83,24 +83,24 @@ export default function AlmRepoItem({
             </LightPrimary>
           )}
         </div>
-        <div className="sw-ml-2 sw-flex sw-items-center sw-truncate">
+        <div className="sw-max-w-[50%] sw-min-w-0 sw-ml-2 sw-flex sw-items-center sw-truncate">
           <LightLabel className="sw-body-sm">{secondaryTextNode}</LightLabel>
         </div>
       </div>
-      <div className="sw-flex sw-justify-between sw-items-center sw-flex-1">
-        {almUrl !== undefined && (
-          <div className="sw-flex sw-items-center">
-            <Link
-              className="sw-body-sm-highlight"
-              onClick={(e) => e.stopPropagation()}
-              target="_blank"
-              to={almUrl}
-              rel="noopener noreferrer"
-            >
-              {almUrlText}
-            </Link>
-          </div>
-        )}
+      {almUrl !== undefined && (
+        <div className="sw-flex sw-items-center sw-flex-shrink-0 sw-ml-2">
+          <Link
+            className="sw-body-sm-highlight"
+            onClick={(e) => e.stopPropagation()}
+            target="_blank"
+            to={almUrl}
+            rel="noopener noreferrer"
+          >
+            {almUrlText ?? almUrl}
+          </Link>
+        </div>
+      )}
+      <div className="sw-ml-2 sw-flex sw-justify-end sw-flex-grow sw-flex-shrink-0 sw-w-abs-150">
         {sqProjectKey ? (
           <div className="sw-flex sw-items-center">
             <CheckIcon />
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
deleted file mode 100644 (file)
index c5355fd..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.
- */
-#create-project {
-  padding-top: 0 !important;
-}
-
-#create-project header {
-  padding-top: 20px;
-  background-color: var(--barBackgroundColor);
-  position: sticky;
-  top: var(--globalNavHeight);
-  z-index: var(--pageMainZIndex);
-}
-
-.create-project-mode-type-alm.disabled img {
-  filter: grayscale(100%);
-}
-
-.create-project-azdo-repo {
-  width: 410px;
-  min-height: 40px;
-  box-sizing: border-box;
-  margin-right: auto;
-}
-
-.create-project-import-bbs .open .boxed-group-header {
-  border-bottom: 1px solid var(--barBorderColor);
-}
-
-.create-project-import-bbs .boxed-group-inner {
-  padding-top: calc(3 * var(--gridSize));
-}
-
-.create-project-import-bbs-repo {
-  width: 250px;
-  min-height: 40px;
-}
-
-.create-project-github-repository {
-  box-sizing: border-box;
-  width: 33.33%;
-}
-
-.create-project-github-repository .notice {
-  display: block;
-  position: absolute;
-}
-
-.create-project-github-repository .notice svg {
-  color: var(--green);
-}
-
-.create-project-import table > tbody > tr > td {
-  vertical-align: middle;
-}
-
-.create-project-import .project-name,
-.create-project-import .project-path {
-  max-width: 400px;
-}
-
-.create-project-import .sq-project-link {
-  max-width: 300px;
-}
-
-.create-project-import .already-set-up svg {
-  color: var(--green);
-}
index c6b6ca90abfcb053e182669c54f146ddf4b6cc59..1268881dd6584467ab06cf51d133c629c0ce9c61 100644 (file)
@@ -3954,6 +3954,7 @@ onboarding.create_project.see_on_github=See on GitHub
 onboarding.create_project.search_prompt=Search for projects
 onboarding.create_project.set_up=Set up
 onboarding.create_project.azure.title=Azure project onboarding
+onboarding.create_project.azure.subtitle=Import projects from one of your Azure projects
 onboarding.create_project.azure.no_projects=No projects could be fetched from Azure DevOps. Contact your system administrator, or {link}.
 onboarding.create_project.azure.search_results_for_project_X=Search results for "{0}"
 onboarding.create_project.azure.no_repositories=Could not fetch repositories for this project. Contact your system administrator, or {link}.