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");