]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16627 Always display Azure repository search results
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Mon, 11 Jul 2022 09:39:53 +0000 (11:39 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 12 Jul 2022 14:30:05 +0000 (14:30 +0000)
server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx
server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx
server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectCreate-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/AzureProjectsList-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AzureProjectsList-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 963e3dd32c8c5673a840ca3cf3496da2fe0aaec1..50144f7441629940b0e09e58cdce0dd526e0c8d8 100644 (file)
@@ -17,7 +17,6 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { groupBy } from 'lodash';
 import * as React from 'react';
 import {
   checkPersonalAccessTokenIsValid,
@@ -50,7 +49,7 @@ interface State {
   projects?: AzureProject[];
   repositories: Dict<AzureRepository[]>;
   searching?: boolean;
-  searchResults?: Dict<AzureRepository[]>;
+  searchResults?: AzureRepository[];
   searchQuery?: string;
   selectedRepository?: AzureRepository;
   settings?: AlmSettingsInstance;
@@ -163,20 +162,20 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
     router.replace(location);
   };
 
-  handleOpenProject = async (projectKey: string) => {
+  handleOpenProject = async (projectName: string) => {
     if (this.state.searchResults) {
       return;
     }
 
     this.setState(({ loadingRepositories }) => ({
-      loadingRepositories: { ...loadingRepositories, [projectKey]: true }
+      loadingRepositories: { ...loadingRepositories, [projectName]: true }
     }));
 
-    const projectRepos = await this.fetchAzureRepositories(projectKey);
+    const projectRepos = await this.fetchAzureRepositories(projectName);
 
     this.setState(({ loadingRepositories, repositories }) => ({
-      loadingRepositories: { ...loadingRepositories, [projectKey]: false },
-      repositories: { ...repositories, [projectKey]: projectRepos }
+      loadingRepositories: { ...loadingRepositories, [projectName]: false },
+      repositories: { ...repositories, [projectName]: projectRepos }
     }));
   };
 
@@ -194,14 +193,17 @@ export default class AzureProjectCreate extends React.PureComponent<Props, State
 
     this.setState({ searching: true });
 
-    const results: AzureRepository[] = await searchAzureRepositories(settings.key, searchQuery)
+    const searchResults: AzureRepository[] = await searchAzureRepositories(
+      settings.key,
+      searchQuery
+    )
       .then(({ repositories }) => repositories)
       .catch(() => []);
 
     if (this.mounted) {
       this.setState({
         searching: false,
-        searchResults: groupBy(results, 'projectName'),
+        searchResults,
         searchQuery
       });
     }
index c4965a8dca79e550fd52d2d9f24fa8312b45823f..a7a424b32f4a2e1bb25f6c764c59c3bc34bfa9c3 100644 (file)
@@ -49,7 +49,7 @@ export interface AzureProjectCreateRendererProps {
   projects?: AzureProject[];
   repositories: Dict<AzureRepository[]>;
   searching?: boolean;
-  searchResults?: Dict<AzureRepository[]>;
+  searchResults?: AzureRepository[];
   searchQuery?: string;
   selectedRepository?: AzureRepository;
   settings?: AlmSettingsInstance;
index 19f384142881c14dcfde13435aaab7b9c9026509..e8b222682c277a0d8879534e3ac0ee38fa24516b 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 { uniqBy } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router-dom';
 import ListFooter from '../../../components/controls/ListFooter';
 import { Alert } from '../../../components/ui/Alert';
-import { translate } from '../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { queryToSearch } from '../../../helpers/urls';
 import { AzureProject, AzureRepository } from '../../../types/alm-integration';
 import { Dict } from '../../../types/types';
@@ -36,7 +37,7 @@ export interface AzureProjectsListProps {
   onSelectRepository: (repository: AzureRepository) => void;
   projects?: AzureProject[];
   repositories: Dict<AzureRepository[]>;
-  searchResults?: Dict<AzureRepository[]>;
+  searchResults?: AzureRepository[];
   searchQuery?: string;
   selectedRepository?: AzureRepository;
 }
@@ -56,36 +57,56 @@ export default function AzureProjectsList(props: AzureProjectsListProps) {
 
   const [page, setPage] = React.useState(1);
 
-  const filteredProjects = searchResults
-    ? projects.filter(p => searchResults[p.name] !== undefined)
-    : projects;
+  if (searchResults && searchResults.length === 0) {
+    return (
+      <Alert className="spacer-top" variant="warning">
+        {translate('onboarding.create_project.azure.no_results')}
+      </Alert>
+    );
+  }
 
-  if (filteredProjects.length === 0) {
+  if (projects.length === 0) {
     return (
       <Alert className="spacer-top" variant="warning">
-        {searchResults ? (
-          translate('onboarding.create_project.azure.no_results')
-        ) : (
-          <FormattedMessage
-            defaultMessage={translate('onboarding.create_project.azure.no_projects')}
-            id="onboarding.create_project.azure.no_projects"
-            values={{
-              link: (
-                <Link
-                  to={{
-                    pathname: '/projects/create',
-                    search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 })
-                  }}>
-                  {translate('onboarding.create_project.update_your_token')}
-                </Link>
-              )
-            }}
-          />
-        )}
+        <FormattedMessage
+          defaultMessage={translate('onboarding.create_project.azure.no_projects')}
+          id="onboarding.create_project.azure.no_projects"
+          values={{
+            link: (
+              <Link
+                to={{
+                  pathname: '/projects/create',
+                  search: queryToSearch({ mode: CreateProjectModes.AzureDevOps, resetPat: 1 })
+                }}>
+                {translate('onboarding.create_project.update_your_token')}
+              </Link>
+            )
+          }}
+        />
       </Alert>
     );
   }
 
+  let filteredProjects: AzureProject[];
+  if (searchResults !== undefined) {
+    filteredProjects = uniqBy(
+      searchResults.map(r => {
+        return (
+          projects.find(p => p.name === r.projectName) || {
+            name: r.projectName,
+            description: translateWithParameters(
+              'onboarding.create_project.azure.search_results_for_project_X',
+              r.projectName
+            )
+          }
+        );
+      }),
+      'name'
+    );
+  } else {
+    filteredProjects = projects;
+  }
+
   const displayedProjects = filteredProjects.slice(0, page * PAGE_SIZE);
 
   // Add a suffix to the key to force react to not reuse AzureProjectAccordions between
@@ -102,7 +123,11 @@ export default function AzureProjectsList(props: AzureProjectsListProps) {
           onOpen={props.onOpenProject}
           onSelectRepository={props.onSelectRepository}
           project={p}
-          repositories={searchResults ? searchResults[p.name] : repositories[p.name]}
+          repositories={
+            searchResults
+              ? searchResults.filter(s => s.projectName === p.name)
+              : repositories[p.name]
+          }
           selectedRepository={selectedRepository}
           searchQuery={searchQuery}
           startsOpen={searchResults !== undefined || i === 0}
index 383fefc2fb0a7b1763596d3c5fe439f602449d52..21ae80c4011834a6a3ba34daff5c8208e295e657 100644 (file)
@@ -155,7 +155,7 @@ it('should handle searching for repositories', async () => {
   expect(searchAzureRepositories).toBeCalledWith('foo', query);
   await waitAndUpdate(wrapper);
   expect(wrapper.state().searching).toBe(false);
-  expect(wrapper.state().searchResults).toEqual({ [repositories[0].projectName]: repositories });
+  expect(wrapper.state().searchResults).toEqual(repositories);
   expect(wrapper.state().searchQuery).toBe(query);
 
   // Ignore opening a project when search results are displayed
index b61f2398f37069fa33c41c2e8494366774ae5b68..60196abb5de551df8b397ede5b66edfea9e9038e 100644 (file)
@@ -35,11 +35,19 @@ it('should render search results correctly', () => {
     mockAzureProject({ name: 'p2', description: 'p2' }),
     mockAzureProject({ name: 'p3', description: 'p3' })
   ];
-  const searchResults = {
-    p2: [mockAzureRepository({ projectName: 'p2' })]
-  };
+  const searchResults = [mockAzureRepository({ projectName: 'p2' })];
   expect(shallowRender({ searchResults, projects })).toMatchSnapshot('default');
-  expect(shallowRender({ searchResults: {}, projects })).toMatchSnapshot('empty');
+  expect(shallowRender({ searchResults: [], projects })).toMatchSnapshot('empty');
+  expect(
+    shallowRender({
+      searchResults: [
+        mockAzureRepository({ projectName: 'p2' }),
+        mockAzureRepository({ name: 'Unknown repository 1', projectName: 'u1' }),
+        mockAzureRepository({ name: 'Unknown repository 2', projectName: 'u2' })
+      ],
+      projects
+    })
+  ).toMatchSnapshot('search results belonging to unknown projects');
 });
 
 it('should handle pagination', () => {
index 271f606cb68d2f179055d47d13ba6bb4b6c631d4..9cfa749b9f646827e3386ce19c59eaf3e690614f 100644 (file)
@@ -91,3 +91,79 @@ exports[`should render search results correctly: empty 1`] = `
   onboarding.create_project.azure.no_results
 </Alert>
 `;
+
+exports[`should render search results correctly: search results belonging to unknown projects 1`] = `
+<div>
+  <AzureProjectAccordion
+    importing={false}
+    key="p2 - result"
+    loading={false}
+    onOpen={[MockFunction]}
+    onSelectRepository={[MockFunction]}
+    project={
+      Object {
+        "description": "p2",
+        "name": "p2",
+      }
+    }
+    repositories={
+      Array [
+        Object {
+          "name": "Azure repo 1",
+          "projectName": "p2",
+        },
+      ]
+    }
+    startsOpen={true}
+  />
+  <AzureProjectAccordion
+    importing={false}
+    key="u1 - result"
+    loading={false}
+    onOpen={[MockFunction]}
+    onSelectRepository={[MockFunction]}
+    project={
+      Object {
+        "description": "onboarding.create_project.azure.search_results_for_project_X.u1",
+        "name": "u1",
+      }
+    }
+    repositories={
+      Array [
+        Object {
+          "name": "Unknown repository 1",
+          "projectName": "u1",
+        },
+      ]
+    }
+    startsOpen={true}
+  />
+  <AzureProjectAccordion
+    importing={false}
+    key="u2 - result"
+    loading={false}
+    onOpen={[MockFunction]}
+    onSelectRepository={[MockFunction]}
+    project={
+      Object {
+        "description": "onboarding.create_project.azure.search_results_for_project_X.u2",
+        "name": "u2",
+      }
+    }
+    repositories={
+      Array [
+        Object {
+          "name": "Unknown repository 2",
+          "projectName": "u2",
+        },
+      ]
+    }
+    startsOpen={true}
+  />
+  <ListFooter
+    count={3}
+    loadMore={[Function]}
+    total={3}
+  />
+</div>
+`;
index 19eca9ff048cabc5e6cc7b2c0f858df6c889e0ce..c2d3c449d9b3c52ca38cd34fcc0aeeb71b373a1b 100644 (file)
@@ -3454,6 +3454,7 @@ onboarding.create_project.search_prompt=Search for projects
 onboarding.create_project.set_up=Set up
 onboarding.create_project.azure.title=Which Azure DevOps repository do you want to set up?
 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}.
 onboarding.create_project.azure.no_results=No repositories match your search query.
 onboarding.create_project.bitbucketcloud.title=Which Bitbucket Cloud repository do you want to set up?