From b8d6c219ee7ee2baa9c9b2b592424925ab8b9638 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 10 Dec 2021 11:04:59 +0100 Subject: [PATCH] Add extension point to branches overview (#1888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare branches overview to show additional branch details. Co-authored-by: René Pfeuffer --- gradle/changelog/branch_details_prs.yaml | 2 + .../sonia/scm/repository/BranchDetails.java | 87 +++++++++++++++++++ .../api/BranchDetailsCommandResult.java | 32 ++----- .../spi/GitBranchDetailsCommand.java | 14 +-- .../spi/GitBranchDetailsCommandTest.java | 7 +- .../spi/HgBranchDetailsCommand.java | 14 +-- .../spi/HgBranchDetailsCommandTest.java | 8 +- scm-ui/ui-api/src/branches.ts | 25 ++++-- scm-ui/ui-components/package.json | 4 +- .../src/SmallLoadingSpinner.stories.tsx | 32 +++++++ .../ui-components/src/SmallLoadingSpinner.tsx | 34 ++++++++ .../src/__snapshots__/storyshots.test.ts.snap | 12 +++ scm-ui/ui-components/src/index.ts | 7 +- scm-ui/ui-extensions/src/extensionPoints.ts | 3 +- .../branches/components/AheadBehindTag.tsx | 2 +- .../repos/branches/components/BranchRow.tsx | 20 +++-- .../repos/branches/components/BranchTable.tsx | 1 + .../api/v2/resources/BranchDetailsMapper.java | 10 +-- .../v2/resources/BranchDetailsResource.java | 4 +- .../v2/resources/BranchDetailsMapperTest.java | 4 +- .../resources/BranchDetailsResourceTest.java | 5 +- 21 files changed, 249 insertions(+), 78 deletions(-) create mode 100644 gradle/changelog/branch_details_prs.yaml create mode 100644 scm-core/src/main/java/sonia/scm/repository/BranchDetails.java create mode 100644 scm-ui/ui-components/src/SmallLoadingSpinner.stories.tsx create mode 100644 scm-ui/ui-components/src/SmallLoadingSpinner.tsx diff --git a/gradle/changelog/branch_details_prs.yaml b/gradle/changelog/branch_details_prs.yaml new file mode 100644 index 0000000000..9ae2349814 --- /dev/null +++ b/gradle/changelog/branch_details_prs.yaml @@ -0,0 +1,2 @@ +- type: added + description: Show additional branch details information ([#1888](https://github.com/scm-manager/scm-manager/pull/1888)) diff --git a/scm-core/src/main/java/sonia/scm/repository/BranchDetails.java b/scm-core/src/main/java/sonia/scm/repository/BranchDetails.java new file mode 100644 index 0000000000..cc12b5b3dd --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/BranchDetails.java @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository; + +import java.util.Optional; + +import static java.util.Optional.ofNullable; + +/** + * Represents details of a branch that are not computed by default for a {@link Branch}. + * + * @since 2.28.0 + */ +public class BranchDetails { + + private final String branchName; + private final Integer changesetsAhead; + private final Integer changesetsBehind; + + /** + * Create the details object without further details. + * + * @param branchName The name of the branch these details are for. + */ + public BranchDetails(String branchName) { + this(branchName, null, null); + } + + /** + * Creates the details object with ahead/behind counts. + * + * @param branchName The name of the branch these details are for. + * @param changesetsAhead The number of changesets this branch is ahead of the default branch (that is + * the number of changesets on this branch that are not reachable from the default branch). + * @param changesetsBehind The number of changesets the default branch is ahead of this branch (that is + */ + public BranchDetails(String branchName, Integer changesetsAhead, Integer changesetsBehind) { + this.branchName = branchName; + this.changesetsAhead = changesetsAhead; + this.changesetsBehind = changesetsBehind; + } + + /** + * The name of the branch these details are for. + */ + public String getBranchName() { + return branchName; + } + + /** + * The number of changesets this branch is ahead of the default branch (that is + * the number of changesets on this branch that are not reachable from the default branch). + */ + public Optional getChangesetsAhead() { + return ofNullable(changesetsAhead); + } + + /** + * The number of changesets the default branch is ahead of this branch (that is + * the number of changesets on the default branch that are not reachable from this branch). + */ + public Optional getChangesetsBehind() { + return ofNullable(changesetsBehind); + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/BranchDetailsCommandResult.java b/scm-core/src/main/java/sonia/scm/repository/api/BranchDetailsCommandResult.java index 484f25758b..5343638d83 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/BranchDetailsCommandResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/BranchDetailsCommandResult.java @@ -24,43 +24,27 @@ package sonia.scm.repository.api; -import java.util.Optional; - -import static java.util.Optional.ofNullable; +import sonia.scm.repository.BranchDetails; /** * @since 2.28.0 */ public class BranchDetailsCommandResult { - private final Integer changesetsAhead; - private final Integer changesetsBehind; + private final BranchDetails details; /** * Creates the result object * - * @param changesetsAhead The number of changesets this branch is ahead of the default branch (that is - * the number of changesets on this branch that are not reachable from the default branch). - * @param changesetsBehind The number of changesets the default branch is ahead of this branch (that is - * the number of changesets on the default branch that are not reachable from this branch). + * @param details The details for the branch */ - public BranchDetailsCommandResult(Integer changesetsAhead, Integer changesetsBehind) { - this.changesetsAhead = changesetsAhead; - this.changesetsBehind = changesetsBehind; + public BranchDetailsCommandResult(BranchDetails details) { + this.details = details; } /** - * The number of changesets this branch is ahead of the default branch (that is - * the number of changesets on this branch that are not reachable from the default branch). + * The details for the branch. */ - public Optional getChangesetsAhead() { - return ofNullable(changesetsAhead); - } - - /** - * The number of changesets the default branch is ahead of this branch (that is - * the number of changesets on the default branch that are not reachable from this branch). - */ - public Optional getChangesetsBehind() { - return ofNullable(changesetsBehind); + public BranchDetails getDetails() { + return details; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchDetailsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchDetailsCommand.java index d98d85b41e..3741040ba8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchDetailsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchDetailsCommand.java @@ -33,6 +33,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalkUtils; import org.eclipse.jgit.revwalk.filter.RevFilter; import sonia.scm.repository.Branch; +import sonia.scm.repository.BranchDetails; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.BranchDetailsCommandResult; @@ -52,14 +53,15 @@ public class GitBranchDetailsCommand extends AbstractGitCommand implements Branc @Override public BranchDetailsCommandResult execute(BranchDetailsCommandRequest branchDetailsCommandRequest) { String defaultBranch = context.getConfig().getDefaultBranch(); - if (branchDetailsCommandRequest.getBranchName().equals(defaultBranch)) { - return new BranchDetailsCommandResult(0, 0); + String branchName = branchDetailsCommandRequest.getBranchName(); + if (branchName.equals(defaultBranch)) { + return new BranchDetailsCommandResult(new BranchDetails(branchName, 0, 0)); } try { Repository repository = open(); - ObjectId branchCommit = getObjectId(branchDetailsCommandRequest.getBranchName(), repository); + ObjectId branchCommit = getObjectId(branchName, repository); ObjectId defaultCommit = getObjectId(defaultBranch, repository); - return computeAheadBehind(repository, branchCommit, defaultCommit); + return computeAheadBehind(repository, branchName, branchCommit, defaultCommit); } catch (IOException e) { throw new InternalRepositoryException(context.getRepository(), "could not compute ahead/behind", e); } @@ -73,7 +75,7 @@ public class GitBranchDetailsCommand extends AbstractGitCommand implements Branc return branchCommit; } - private BranchDetailsCommandResult computeAheadBehind(Repository repository, ObjectId branchCommit, ObjectId defaultCommit) throws MissingObjectException, IncorrectObjectTypeException { + private BranchDetailsCommandResult computeAheadBehind(Repository repository, String branchName, ObjectId branchCommit, ObjectId defaultCommit) throws MissingObjectException, IncorrectObjectTypeException { // this implementation is a copy of the implementation in org.eclipse.jgit.lib.BranchTrackingStatus try (RevWalk walk = new RevWalk(repository)) { @@ -90,7 +92,7 @@ public class GitBranchDetailsCommand extends AbstractGitCommand implements Branc int aheadCount = RevWalkUtils.count(walk, localCommit, mergeBase); int behindCount = RevWalkUtils.count(walk, trackingCommit, mergeBase); - return new BranchDetailsCommandResult(aheadCount, behindCount); + return new BranchDetailsCommandResult(new BranchDetails(branchName, aheadCount, behindCount)); } catch (IOException e) { throw new InternalRepositoryException(context.getRepository(), "could not compute ahead/behind", e); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchDetailsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchDetailsCommandTest.java index 9a7c69f966..3683f844e9 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchDetailsCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBranchDetailsCommandTest.java @@ -26,6 +26,7 @@ package sonia.scm.repository.spi; import org.junit.Test; import sonia.scm.NotFoundException; +import sonia.scm.repository.BranchDetails; import sonia.scm.repository.api.BranchDetailsCommandResult; import static org.assertj.core.api.Assertions.assertThat; @@ -38,7 +39,7 @@ public class GitBranchDetailsCommandTest extends AbstractGitCommandTestBase { BranchDetailsCommandRequest request = new BranchDetailsCommandRequest(); request.setBranchName("master"); - BranchDetailsCommandResult result = command.execute(request); + BranchDetails result = command.execute(request).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(0); assertThat(result.getChangesetsBehind()).get().isEqualTo(0); @@ -50,7 +51,7 @@ public class GitBranchDetailsCommandTest extends AbstractGitCommandTestBase { BranchDetailsCommandRequest request = new BranchDetailsCommandRequest(); request.setBranchName("test-branch"); - BranchDetailsCommandResult result = command.execute(request); + BranchDetails result = command.execute(request).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(1); assertThat(result.getChangesetsBehind()).get().isEqualTo(2); @@ -62,7 +63,7 @@ public class GitBranchDetailsCommandTest extends AbstractGitCommandTestBase { BranchDetailsCommandRequest request = new BranchDetailsCommandRequest(); request.setBranchName("partially_merged"); - BranchDetailsCommandResult result = command.execute(request); + BranchDetails result = command.execute(request).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(3); assertThat(result.getChangesetsBehind()).get().isEqualTo(1); diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchDetailsCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchDetailsCommand.java index d9261fd639..d0ed4e22c3 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchDetailsCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBranchDetailsCommand.java @@ -28,6 +28,7 @@ import org.javahg.Changeset; import org.javahg.commands.ExecutionException; import org.javahg.commands.LogCommand; import sonia.scm.repository.Branch; +import sonia.scm.repository.BranchDetails; import sonia.scm.repository.api.BranchDetailsCommandResult; import javax.inject.Inject; @@ -51,18 +52,19 @@ public class HgBranchDetailsCommand implements BranchDetailsCommand { @Override public BranchDetailsCommandResult execute(BranchDetailsCommandRequest request) { - if (request.getBranchName().equals(DEFAULT_BRANCH_NAME)) { - return new BranchDetailsCommandResult(0,0); + final String branchName = request.getBranchName(); + if (branchName.equals(DEFAULT_BRANCH_NAME)) { + return new BranchDetailsCommandResult(new BranchDetails(branchName, 0, 0)); } try { - List behind = getChangesetsSolelyOnBranch(DEFAULT_BRANCH_NAME, request.getBranchName()); - List ahead = getChangesetsSolelyOnBranch(request.getBranchName(), DEFAULT_BRANCH_NAME); + List behind = getChangesetsSolelyOnBranch(DEFAULT_BRANCH_NAME, branchName); + List ahead = getChangesetsSolelyOnBranch(branchName, DEFAULT_BRANCH_NAME); - return new BranchDetailsCommandResult(ahead.size(), behind.size()); + return new BranchDetailsCommandResult(new BranchDetails(branchName, ahead.size(), behind.size())); } catch (ExecutionException e) { if (e.getMessage().contains("unknown revision '")) { - throw notFound(entity(Branch.class, request.getBranchName()).in(context.getScmRepository())); + throw notFound(entity(Branch.class, branchName).in(context.getScmRepository())); } throw e; } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchDetailsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchDetailsCommandTest.java index 32c972884a..1ff0af7188 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchDetailsCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgBranchDetailsCommandTest.java @@ -26,7 +26,7 @@ package sonia.scm.repository.spi; import org.junit.Test; import sonia.scm.NotFoundException; -import sonia.scm.repository.api.BranchDetailsCommandResult; +import sonia.scm.repository.BranchDetails; import static org.assertj.core.api.Assertions.assertThat; @@ -37,7 +37,7 @@ public class HgBranchDetailsCommandTest extends AbstractHgCommandTestBase { BranchDetailsCommandRequest branchRequest = new BranchDetailsCommandRequest(); branchRequest.setBranchName("testbranch"); - BranchDetailsCommandResult result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest); + BranchDetails result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(1); assertThat(result.getChangesetsBehind()).get().isEqualTo(3); @@ -48,7 +48,7 @@ public class HgBranchDetailsCommandTest extends AbstractHgCommandTestBase { BranchDetailsCommandRequest branchRequest = new BranchDetailsCommandRequest(); branchRequest.setBranchName("with_merge"); - BranchDetailsCommandResult result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest); + BranchDetails result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(5); assertThat(result.getChangesetsBehind()).get().isEqualTo(1); @@ -59,7 +59,7 @@ public class HgBranchDetailsCommandTest extends AbstractHgCommandTestBase { BranchDetailsCommandRequest branchRequest = new BranchDetailsCommandRequest(); branchRequest.setBranchName("next_merge"); - BranchDetailsCommandResult result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest); + BranchDetails result = new HgBranchDetailsCommand(cmdContext).execute(branchRequest).getDetails(); assertThat(result.getChangesetsAhead()).get().isEqualTo(3); assertThat(result.getChangesetsBehind()).get().isEqualTo(0); diff --git a/scm-ui/ui-api/src/branches.ts b/scm-ui/ui-api/src/branches.ts index cd6147331e..389a65237a 100644 --- a/scm-ui/ui-api/src/branches.ts +++ b/scm-ui/ui-api/src/branches.ts @@ -26,7 +26,7 @@ import { BranchCollection, BranchCreation, BranchDetailsCollection, - Link, + Link, NamespaceAndName, Repository } from "@scm-manager/ui-types"; import { requiredLink } from "./links"; @@ -61,13 +61,6 @@ export const useBranch = (repository: Repository, name: string): ApiResultWithFe ); }; -export const useBranchDetails = (repository: Repository, branch: string) => { - const link = requiredLink(repository, "branchDetails"); - return useQuery(branchQueryKey(repository, branch, "details"), () => - apiClient.get(concat(link, encodeURIComponent(branch))).then(response => response.json()) - ); -}; - function chunkBranches(branches: Branch[]) { const chunks: Branch[][] = []; const chunkSize = 5; @@ -84,6 +77,20 @@ function chunkBranches(branches: Branch[]) { return chunks; } +const branchDetailsQueryKey = ( + repository: NamespaceAndName, + branch: string | undefined = undefined, + ...values: unknown[] +) => { + let branchName; + if (!branch) { + branchName = "_"; + } else { + branchName = branch; + } + return [...repoQueryKey(repository), "branch-details", branchName, ...values]; +}; + export const useBranchDetailsCollection = (repository: Repository, branches: Branch[]) => { const link = requiredLink(repository, "branchDetailsCollection"); const chunks = chunkBranches(branches); @@ -93,7 +100,7 @@ export const useBranchDetailsCollection = (repository: Repository, branches: Bra Error, BranchDetailsCollection >( - branchQueryKey(repository, "details"), + branchDetailsQueryKey(repository), ({ pageParam = 0 }) => { const encodedBranches = chunks[pageParam]?.map(b => encodeURIComponent(b.name)).join("&branches="); return apiClient.get(concat(link, `?branches=${encodedBranches}`)).then(response => response.json()); diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index cb873700ff..6ff7f493fa 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -60,7 +60,6 @@ "sass-loader": "^12.3.0", "storybook-addon-i18next": "^1.3.0", "storybook-addon-themes": "^6.1.0", - "tabbable": "^5.2.1", "to-camel-case": "^1.0.0", "webpack": "^5.61.0", "worker-plugin": "^3.2.0" @@ -92,7 +91,8 @@ "remark-gfm": "^1.0.0", "remark-parse": "^9.0.0", "remark-rehype": "^8.0.0", - "unified": "^9.2.1" + "unified": "^9.2.1", + "tabbable": "^5.2.1" }, "babel": { "presets": [ diff --git a/scm-ui/ui-components/src/SmallLoadingSpinner.stories.tsx b/scm-ui/ui-components/src/SmallLoadingSpinner.stories.tsx new file mode 100644 index 0000000000..957ae8dfd0 --- /dev/null +++ b/scm-ui/ui-components/src/SmallLoadingSpinner.stories.tsx @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import { storiesOf } from "@storybook/react"; +import React from "react"; +import SmallLoadingSpinner from "./SmallLoadingSpinner"; + +storiesOf("SmallLoading", module).add("Default", () => ( +
+ +
+)); diff --git a/scm-ui/ui-components/src/SmallLoadingSpinner.tsx b/scm-ui/ui-components/src/SmallLoadingSpinner.tsx new file mode 100644 index 0000000000..85eae8ec72 --- /dev/null +++ b/scm-ui/ui-components/src/SmallLoadingSpinner.tsx @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import React, { FC } from "react"; + +const SmallLoadingSpinner: FC = () => { + return ( +
+
+
+ ); +}; + +export default SmallLoadingSpinner; diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 91457dfb63..9445f16f0e 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -75216,6 +75216,18 @@ exports[`Storyshots Secondary Navigation Sub Navigation 1`] = `
`; +exports[`Storyshots SmallLoading Default 1`] = ` +
+
+
+
+
+`; + exports[`Storyshots SplitAndReplace Simple replacement 1`] = ` Array [
= ({ branch, details }) => { message={t("branch.aheadBehind.tooltip", { ahead: details.changesetsAhead, behind: details.changesetsBehind })} location="top" > -
+
{details.changesetsBehind} void; details?: BranchDetails; }; -const BranchRow: FC = ({ baseUrl, branch, onDelete, details }) => { +const BranchRow: FC = ({ repository, baseUrl, branch, onDelete, details }) => { const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`; const [t] = useTranslation("repos"); @@ -62,11 +64,7 @@ const BranchRow: FC = ({ baseUrl, branch, onDelete, details }) => { if (details) { return ; } - return ( -
-
-
- ); + return ; }; const committedAt = ( @@ -86,9 +84,10 @@ const BranchRow: FC = ({ baseUrl, branch, onDelete, details }) => { committedAtBy = committedAt; } + const extensionProps = { repository, branch, details }; return ( - + {branch.name} @@ -99,6 +98,9 @@ const BranchRow: FC = ({ baseUrl, branch, onDelete, details }) => { )} {renderBranchTag()} + {binder.hasExtension("repos.branches.row.details") + ? binder.getExtensions("repos.branches.row.details").map(e => {React.createElement(e, extensionProps)}) + : null} {deleteButton} ); diff --git a/scm-ui/ui-webapp/src/repos/branches/components/BranchTable.tsx b/scm-ui/ui-webapp/src/repos/branches/components/BranchTable.tsx index cdbb4257f2..c1c25654e3 100644 --- a/scm-ui/ui-webapp/src/repos/branches/components/BranchTable.tsx +++ b/scm-ui/ui-webapp/src/repos/branches/components/BranchTable.tsx @@ -99,6 +99,7 @@ const BranchTable: FC = ({ repository, baseUrl, branches, type, branchesD {(branches || []).map(branch => ( { +public abstract class BranchDetailsMapper extends BaseMapper { @Inject private ResourceLinks resourceLinks; - abstract BranchDetailsDto map(@Context Repository repository, String branchName, BranchDetailsCommandResult result); + abstract BranchDetailsDto map(@Context Repository repository, String branchName, BranchDetails result); @ObjectFactory - BranchDetailsDto createDto(@Context Repository repository, String branchName, BranchDetailsCommandResult result) { + BranchDetailsDto createDto(@Context Repository repository, String branchName, BranchDetails result) { Links.Builder linksBuilder = createLinks(repository, branchName); Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder(); - applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), result, branchName, repository); + applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), result, repository); return new BranchDetailsDto(linksBuilder.build(), embeddedBuilder.build()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDetailsResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDetailsResource.java index 8a57c1f804..8b873adcd3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDetailsResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDetailsResource.java @@ -117,7 +117,7 @@ public class BranchDetailsResource { ) { try (RepositoryService service = serviceFactory.create(new NamespaceAndName(namespace, name))) { BranchDetailsCommandResult result = service.getBranchDetailsCommand().execute(branchName); - BranchDetailsDto dto = mapper.map(service.getRepository(), branchName, result); + BranchDetailsDto dto = mapper.map(service.getRepository(), branchName, result.getDetails()); return Response.ok(dto).build(); } catch (CommandNotSupportedException ex) { return Response.status(Response.Status.BAD_REQUEST).build(); @@ -186,7 +186,7 @@ public class BranchDetailsResource { for (String branch : branches) { try { BranchDetailsCommandResult result = branchDetailsCommand.execute(branch); - dtos.add(mapper.map(service.getRepository(), branch, result)); + dtos.add(mapper.map(service.getRepository(), branch, result.getDetails())); } catch (NotFoundException e) { // we simply omit details for branches that do not exist } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsMapperTest.java index efec787d33..f1659074e4 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsMapperTest.java @@ -27,9 +27,9 @@ package sonia.scm.api.v2.resources; import com.google.inject.util.Providers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import sonia.scm.repository.BranchDetails; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryTestData; -import sonia.scm.repository.api.BranchDetailsCommandResult; import java.net.URI; @@ -53,7 +53,7 @@ class BranchDetailsMapperTest { BranchDetailsDto dto = mapper.map( repository, "master", - new BranchDetailsCommandResult(42, 21) + new BranchDetails("master", 42, 21) ); assertThat(dto.getBranchName()).isEqualTo("master"); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsResourceTest.java index 6c5d18cfca..cd042fd430 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/BranchDetailsResourceTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.NotFoundException; +import sonia.scm.repository.BranchDetails; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryTestData; import sonia.scm.repository.api.BranchDetailsCommandBuilder; @@ -49,6 +50,7 @@ import java.net.URI; import java.net.URISyntaxException; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -96,7 +98,7 @@ class BranchDetailsResourceTest extends RepositoryTestBase { when(serviceFactory.create(repository.getNamespaceAndName())).thenReturn(service); when(service.getRepository()).thenReturn(repository); when(service.getBranchDetailsCommand()).thenReturn(branchDetailsCommandBuilder); - BranchDetailsCommandResult result = new BranchDetailsCommandResult(42, 21); + BranchDetailsCommandResult result = new BranchDetailsCommandResult(new BranchDetails("master", 42, 21)); when(branchDetailsCommandBuilder.execute("master")).thenReturn(result); MockHttpRequest request = MockHttpRequest @@ -141,6 +143,7 @@ class BranchDetailsResourceTest extends RepositoryTestBase { when(serviceFactory.create(repository.getNamespaceAndName())).thenReturn(service); when(service.getRepository()).thenReturn(repository); when(service.getBranchDetailsCommand()).thenReturn(branchDetailsCommandBuilder); + when(branchDetailsCommandBuilder.execute(any())).thenAnswer(invocation -> new BranchDetailsCommandResult(new BranchDetails(invocation.getArgument(0, String.class), null, null))); MockHttpRequest request = MockHttpRequest .get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + repository.getNamespaceAndName() + "/branch-details?branches=master&branches=develop&branches=feature%2Fhitchhiker42");