From 6ba792e5bc2dbd57532781d1e9359c5e244acadd Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 15 Dec 2022 11:16:01 +0100 Subject: [PATCH] Adds a new extension point `repository.overview.listOptions` that can be used to set the page size, whether to list archived repositories or not and potentially other options for the repository overview. If no extension is bound, the default values will be used. Committed-by: Eduard Heimbuch --- .../extension_point_for_page_size.yaml | 2 ++ scm-ui/ui-api/src/repositories.test.ts | 28 +++++++++++++++ scm-ui/ui-api/src/repositories.ts | 8 +++++ scm-ui/ui-extensions/src/extensionPoints.ts | 8 +++++ .../src/repos/containers/Overview.tsx | 9 ++++- .../RepositoryCollectionResource.java | 19 ++++++---- .../resources/RepositoryRootResourceTest.java | 36 ++++++++++++++++--- 7 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 gradle/changelog/extension_point_for_page_size.yaml diff --git a/gradle/changelog/extension_point_for_page_size.yaml b/gradle/changelog/extension_point_for_page_size.yaml new file mode 100644 index 0000000000..88ca48412d --- /dev/null +++ b/gradle/changelog/extension_point_for_page_size.yaml @@ -0,0 +1,2 @@ +- type: added + description: Extension point for page size diff --git a/scm-ui/ui-api/src/repositories.test.ts b/scm-ui/ui-api/src/repositories.test.ts index ee1f1852d5..38252ac031 100644 --- a/scm-ui/ui-api/src/repositories.test.ts +++ b/scm-ui/ui-api/src/repositories.test.ts @@ -125,6 +125,34 @@ describe("Test repository hooks", () => { }); }); + it("should hide archived repositories", async () => { + const queryClient = createInfiniteCachingClient(); + setIndexLink(queryClient, "repositories", "/repos"); + fetchMock.get("/api/v2/repos", repositoryCollection, { + query: { + showArchived: "false" + } + }); + + await expectCollection(queryClient, { + showArchived: false + }); + }); + + it("should set the page size", async () => { + const queryClient = createInfiniteCachingClient(); + setIndexLink(queryClient, "repositories", "/repos"); + fetchMock.get("/api/v2/repos", repositoryCollection, { + query: { + pageSize: "25" + } + }); + + await expectCollection(queryClient, { + pageSize: 25 + }); + }); + it("should append search query", async () => { const queryClient = createInfiniteCachingClient(); setIndexLink(queryClient, "repositories", "/repos"); diff --git a/scm-ui/ui-api/src/repositories.ts b/scm-ui/ui-api/src/repositories.ts index 29418c572d..abe0bf673a 100644 --- a/scm-ui/ui-api/src/repositories.ts +++ b/scm-ui/ui-api/src/repositories.ts @@ -47,6 +47,8 @@ export type UseRepositoriesRequest = { search?: string; page?: number | string; disabled?: boolean; + pageSize?: number; + showArchived?: boolean; }; export const useRepositories = (request?: UseRepositoriesRequest): ApiResult => { @@ -59,6 +61,12 @@ export const useRepositories = (request?: UseRepositoriesRequest): ApiResult; +/** + * Specify options for the repository overview like the page size + */ +export type RepositoryOverviewListOptionsExtensionPoint = ExtensionPointDefinition< + "repository.overview.listOptions", + () => { pageSize?: number; showArchived?: boolean } +>; + // From docs export type AdminNavigation = RenderableExtensionPointDefinition<"admin.navigation", { links: Links; url: string }>; diff --git a/scm-ui/ui-webapp/src/repos/containers/Overview.tsx b/scm-ui/ui-webapp/src/repos/containers/Overview.tsx index 5af7ddb4d5..975f5c76bd 100644 --- a/scm-ui/ui-webapp/src/repos/containers/Overview.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/Overview.tsx @@ -37,7 +37,7 @@ import { import RepositoryList from "../components/list"; import { useNamespaceAndNameContext, useNamespaces, useRepositories } from "@scm-manager/ui-api"; import { NamespaceCollection, RepositoryCollection } from "@scm-manager/ui-types"; -import { ExtensionPoint, extensionPoints, useBinder } from "@scm-manager/ui-extensions"; +import { binder, ExtensionPoint, extensionPoints, useBinder } from "@scm-manager/ui-extensions"; import styled from "styled-components"; const StickyColumn = styled.div` @@ -64,12 +64,19 @@ const useOverviewData = () => { const location = useLocation(); const search = urls.getQueryStringFromLocation(location); + const listOptions = binder.getExtension( + "repository.overview.listOptions" + ); + + const listOptionsValue = listOptions ? listOptions() : { pageSize: 10, showArchived: true }; + const request = { namespace: namespaces?._embedded.namespaces.find((n) => n.namespace === namespace), // ui starts counting by 1, // but backend starts counting by 0 page: page - 1, search, + ...listOptionsValue, // if a namespaces is selected we have to wait // until the list of namespaces are loaded from the server // also do not fetch repositories if an invalid namespace is selected diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index 19d2daf8bd..e8ded70fe9 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -112,9 +112,10 @@ public class RepositoryCollectionResource { @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize, @QueryParam("sortBy") String sortBy, @DefaultValue("false") @QueryParam("desc") boolean desc, + @DefaultValue("true") @QueryParam("showArchived") boolean showArchived, @DefaultValue("") @QueryParam("q") String search ) { - return adapter.getAll(page, pageSize, createSearchPredicate(search), sortBy, desc, + return adapter.getAll(page, pageSize, createSearchPredicate(search, showArchived), sortBy, desc, pageResult -> repositoryCollectionToDtoMapper.map(page, pageSize, pageResult)); } @@ -144,9 +145,10 @@ public class RepositoryCollectionResource { @DefaultValue("" + DEFAULT_PAGE_SIZE) @QueryParam("pageSize") int pageSize, @QueryParam("sortBy") String sortBy, @DefaultValue("false") @QueryParam("desc") boolean desc, + @DefaultValue("true") @QueryParam("showArchived") boolean showArchived, @DefaultValue("") @QueryParam("q") String search ) { - return adapter.getAll(page, pageSize, createSearchPredicate(namespace, search), sortBy, desc, + return adapter.getAll(page, pageSize, createSearchPredicate(namespace, search, showArchived), sortBy, desc, pageResult -> repositoryCollectionToDtoMapper.map(namespace, page, pageSize, pageResult)); } @@ -221,21 +223,24 @@ public class RepositoryCollectionResource { return SecurityUtils.getSubject().getPrincipals().oneByType(User.class).getName(); } - private Predicate createSearchPredicate(String namespace, String search) { - if (isNullOrEmpty(search)) { + private Predicate createSearchPredicate(String namespace, String search, boolean showArchived) { + if (isNullOrEmpty(search) && showArchived) { return repository -> repository.getNamespace().equals(namespace); } SearchRequest searchRequest = new SearchRequest(search, true); return repository -> repository.getNamespace().equals(namespace) + && (showArchived || !repository.isArchived()) && SearchUtil.matchesOne(searchRequest, repository.getName(), repository.getNamespace(), repository.getDescription()); } - private Predicate createSearchPredicate(String search) { - if (isNullOrEmpty(search)) { + private Predicate createSearchPredicate(String search, boolean showArchived) { + if (isNullOrEmpty(search) && showArchived) { return user -> true; } SearchRequest searchRequest = new SearchRequest(search, true); - return repository -> SearchUtil.matchesOne(searchRequest, repository.getName(), repository.getNamespace(), repository.getDescription()); + return repository -> + (showArchived || !repository.isArchived()) + && SearchUtil.matchesOne(searchRequest, repository.getName(), repository.getNamespace(), repository.getDescription()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 7b02177c22..0934c2fb57 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -90,6 +90,7 @@ import java.time.Instant; import java.util.Set; import java.util.function.Predicate; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.stream.Stream.of; import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED; @@ -223,7 +224,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { @Test public void shouldFindExistingRepository() throws URISyntaxException, UnsupportedEncodingException { createRepository("space", "repo"); - when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo"); @@ -237,7 +237,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException { PageResult singletonPageResult = createSingletonPageResult(createRepository("space", "repo")); when(repositoryManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); - when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2); @@ -247,11 +246,40 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { assertTrue(response.getContentAsString().contains("\"name\":\"repo\"")); } + @Test + public void shouldGetAllButArchived() throws URISyntaxException { + PageResult singletonPageResult = new PageResult<>(emptyList(), 0); + when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?showArchived=false"); + + dispatcher.invoke(request, response); + + Predicate predicate = filterCaptor.getValue(); + Repository repository = createRepository("hitchhiker", "hog"); + assertThat(predicate.test(repository)).isTrue(); + repository.setArchived(true); + assertThat(predicate.test(repository)).isFalse(); + } + + @Test + public void shouldGetAllIncludingArchivedByDefault() throws URISyntaxException { + PageResult singletonPageResult = new PageResult<>(emptyList(), 0); + when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); + MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2); + + dispatcher.invoke(request, response); + + Predicate predicate = filterCaptor.getValue(); + Repository repository = createRepository("hitchhiker", "hog"); + assertThat(predicate.test(repository)).isTrue(); + repository.setArchived(true); + assertThat(predicate.test(repository)).isTrue(); + } + @Test public void shouldCreateFilterForSearch() throws URISyntaxException { PageResult singletonPageResult = createSingletonPageResult(createRepository("space", "repo")); when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); - when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep"); @@ -267,7 +295,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { public void shouldCreateFilterForNamespace() throws URISyntaxException { PageResult singletonPageResult = createSingletonPageResult(createRepository("space", "repo")); when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); - when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space"); @@ -283,7 +310,6 @@ public class RepositoryRootResourceTest extends RepositoryTestBase { public void shouldCreateFilterForNamespaceWithQuery() throws URISyntaxException { PageResult singletonPageResult = createSingletonPageResult(createRepository("space", "repo")); when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); - when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space?q=Rep");