diff --git a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommandRequest.java b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommandRequest.java index 3dbe71f322..e9bdde76a9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommandRequest.java +++ b/scm-core/src/main/java/sonia/scm/repository/spi/BrowseCommandRequest.java @@ -49,7 +49,7 @@ import java.util.function.Consumer; public final class BrowseCommandRequest extends FileBaseCommandRequest { - public static final int DEFAULT_REQUEST_LIMIT = 1000; + public static final int DEFAULT_REQUEST_LIMIT = 100; private static final long serialVersionUID = 7956624623516803183L; private int proceedFrom; diff --git a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx index 1a5a8b5b58..5163bc150a 100644 --- a/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx +++ b/scm-ui/ui-webapp/src/repos/sources/components/FileTree.tsx @@ -9,6 +9,7 @@ import { File, Repository } from "@scm-manager/ui-types"; import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components"; import { fetchSources, getFetchSourcesFailure, getSources, isFetchSourcesPending } from "../modules/sources"; import FileTreeLeaf from "./FileTreeLeaf"; +import queryString from "query-string"; type Props = WithTranslation & { loading: boolean; @@ -18,6 +19,7 @@ type Props = WithTranslation & { revision: string; path: string; baseUrl: string; + location: any; updateSources: () => void; @@ -85,7 +87,7 @@ class FileTree extends React.Component { } renderSourcesTable() { - const { tree, revision, path, baseUrl, t } = this.props; + const { tree, revision, path, baseUrl, t, location } = this.props; const files = []; @@ -119,6 +121,7 @@ class FileTree extends React.Component { } if (files && files.length > 0) { + let baseUrlWithRevision = baseUrl; if (revision) { baseUrlWithRevision += "/" + encodeURIComponent(revision); @@ -126,24 +129,32 @@ class FileTree extends React.Component { baseUrlWithRevision += "/" + encodeURIComponent(tree.revision); } + const proceedFrom = queryString.parse(location.search).proceedFrom; + if (proceedFrom) { + baseUrlWithRevision += "?proceedFrom=" + proceedFrom; + } + return ( - - - - - - - - - {binder.hasExtension("repos.sources.tree.row.right") && - - - {files.map(file => ( - - ))} - -
{t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.commitDate")}{t("sources.file-tree.description")}} -
+ <> + + + + + + + + + {binder.hasExtension("repos.sources.tree.row.right") && + + + {files.map((file: any) => ( + + ))} + +
{t("sources.file-tree.name")}{t("sources.file-tree.length")}{t("sources.file-tree.commitDate")}{t("sources.file-tree.description")}} +
+ {tree.truncated &&

TRUNCATED

} + ); } return {t("sources.noSources")}; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java index bb74bc5045..9260343065 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BaseFileObjectDtoMapper.java @@ -10,6 +10,7 @@ import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.SubRepository; +import sonia.scm.repository.spi.BrowseCommandRequest; import javax.inject.Inject; @@ -30,7 +31,7 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); @ObjectFactory - FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, FileObject fileObject) { + FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, @Context Integer proceedFrom, FileObject fileObject) { String path = removeFirstSlash(fileObject.getPath()); Links.Builder links = Links.linkingTo(); if (fileObject.isDirectory()) { @@ -39,6 +40,9 @@ abstract class BaseFileObjectDtoMapper extends HalAppenderMapper implements Inst links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)); links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path))); } + if (fileObject.isTruncated()) { + links.single(link("proceed", resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path) + "?proceedFrom=" + (proceedFrom + BrowseCommandRequest.DEFAULT_REQUEST_LIMIT))); + } Embedded.Builder embeddedBuilder = embeddedBuilder(); applyEnrichers(links, embeddedBuilder, namespaceAndName, browserResult, fileObject); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java index f9304881e7..4ff1002367 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapper.java @@ -1,14 +1,19 @@ package sonia.scm.api.v2.resources; +import com.google.common.annotations.VisibleForTesting; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.ObjectFactory; import org.mapstruct.Qualifier; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.FileObject; import sonia.scm.repository.NamespaceAndName; +import sonia.scm.repository.SubRepository; +import sonia.scm.repository.spi.BrowseCommand; +import sonia.scm.repository.spi.BrowseCommandRequest; import javax.inject.Inject; import java.lang.annotation.ElementType; @@ -19,19 +24,23 @@ import java.time.Instant; import java.util.Optional; import java.util.OptionalLong; +import static de.otto.edison.hal.Embedded.embeddedBuilder; +import static de.otto.edison.hal.Link.link; + @Mapper public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectDtoMapper { - FileObjectDto map(BrowserResult browserResult, @Context NamespaceAndName namespaceAndName) { - FileObjectDto fileObjectDto = fileObjectToDto(browserResult.getFile(), namespaceAndName, browserResult); + FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName, int proceedFrom) { + FileObjectDto fileObjectDto = fileObjectToDto(browserResult.getFile(), namespaceAndName, browserResult, proceedFrom); fileObjectDto.setRevision(browserResult.getRevision()); + return fileObjectDto; } @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes @Mapping(target = "children", qualifiedBy = Children.class) @Children - protected abstract FileObjectDto fileObjectToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult); + protected abstract FileObjectDto fileObjectToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, @Context Integer proceedFrom); @Override void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java index b273f241dc..1d39c90f37 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/FileObjectDto.java @@ -15,7 +15,6 @@ import java.util.OptionalLong; @Getter @Setter -@NoArgsConstructor public class FileObjectDto extends HalRepresentation { private String name; private String path; @@ -31,6 +30,7 @@ public class FileObjectDto extends HalRepresentation { private String revision; private boolean partialResult; private boolean computationAborted; + private boolean truncated; public FileObjectDto(Links links, Embedded embedded) { super(links, embedded); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java index 758afd7660..10f7cfd47d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/SourceRootResource.java @@ -8,11 +8,12 @@ import sonia.scm.repository.api.RepositoryServiceFactory; import sonia.scm.web.VndMediaType; import javax.inject.Inject; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; +import javax.ws.rs.QueryParam; import java.io.IOException; import java.net.URLDecoder; @@ -34,36 +35,37 @@ public class SourceRootResource { @GET @Produces(VndMediaType.SOURCE) @Path("") - public Response getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException { - return getSource(namespace, name, "/", null); + public FileObjectDto getAllWithoutRevision(@PathParam("namespace") String namespace, @PathParam("name") String name, @DefaultValue("0") @QueryParam("proceedFrom") int proceedFrom) throws IOException { + return getSource(namespace, name, "/", null, proceedFrom); } @GET @Produces(VndMediaType.SOURCE) @Path("{revision}") - public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision) throws IOException { - return getSource(namespace, name, "/", revision); + public FileObjectDto getAll(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @DefaultValue("0") @QueryParam("proceedFrom") int proceedFrom) throws IOException { + return getSource(namespace, name, "/", revision, proceedFrom); } @GET @Produces(VndMediaType.SOURCE) @Path("{revision}/{path: .*}") - public Response get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path) throws IOException { - return getSource(namespace, name, path, revision); + public FileObjectDto get(@PathParam("namespace") String namespace, @PathParam("name") String name, @PathParam("revision") String revision, @PathParam("path") String path, @DefaultValue("0") @QueryParam("proceedFrom") int proceedFrom) throws IOException { + return getSource(namespace, name, path, revision, proceedFrom); } - private Response getSource(String namespace, String repoName, String path, String revision) throws IOException { + private FileObjectDto getSource(String namespace, String repoName, String path, String revision, int proceedFrom) throws IOException { NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, repoName); try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) { BrowseCommandBuilder browseCommand = repositoryService.getBrowseCommand(); browseCommand.setPath(path); + browseCommand.setProceedFrom(proceedFrom); if (revision != null && !revision.isEmpty()) { browseCommand.setRevision(URLDecoder.decode(revision, "UTF-8")); } BrowserResult browserResult = browseCommand.getBrowserResult(); if (browserResult != null) { - return Response.ok(browserResultToFileObjectDtoMapper.map(browserResult, namespaceAndName)).build(); + return browserResultToFileObjectDtoMapper.map(browserResult, namespaceAndName, proceedFrom); } else { throw notFound(entity("Source", path).in("Revision", revision).in(namespaceAndName)); } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java index 273cc25018..dc264530fd 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BrowserResultToFileObjectDtoMapperTest.java @@ -66,7 +66,7 @@ public class BrowserResultToFileObjectDtoMapperTest { public void shouldMapAttributesCorrectly() { BrowserResult browserResult = createBrowserResult(); - FileObjectDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar")); + FileObjectDto dto = mapper.map(browserResult, new NamespaceAndName("foo", "bar"), 0); assertEqualAttributes(browserResult, dto); } @@ -76,7 +76,7 @@ public class BrowserResultToFileObjectDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - FileObjectDto dto = mapper.map(browserResult, namespaceAndName); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName, 0); assertThat(dto.getEmbedded().getItemsBy("children")).hasSize(2); } @@ -86,7 +86,7 @@ public class BrowserResultToFileObjectDtoMapperTest { BrowserResult browserResult = createBrowserResult(); NamespaceAndName namespaceAndName = new NamespaceAndName("foo", "bar"); - FileObjectDto dto = mapper.map(browserResult, namespaceAndName); + FileObjectDto dto = mapper.map(browserResult, namespaceAndName, 0); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).contains("path"); }