Distinct between active and stale branches

This commit is contained in:
René Pfeuffer
2020-11-23 16:40:26 +01:00
parent 541933a03a
commit 9d25a93558
9 changed files with 113 additions and 10 deletions

View File

@@ -32,9 +32,13 @@ import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable; import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.time.Instant.now;
/** /**
* Represents a branch in a repository. * Represents a branch in a repository.
* *
@@ -190,4 +194,13 @@ public final class Branch implements Serializable, Validateable {
public Optional<Long> getLastCommitDate() { public Optional<Long> getLastCommitDate() {
return Optional.ofNullable(lastCommitDate); return Optional.ofNullable(lastCommitDate);
} }
public boolean isStale() {
return getLastCommitDate()
.map(Instant::ofEpochMilli)
.map(d -> Duration.between(d, now()))
.map(d -> d.toDays())
.map(d -> d >= 14)
.orElse(false);
}
} }

View File

@@ -0,0 +1,62 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.time.temporal.ChronoUnit;
import static java.time.Instant.now;
import static org.assertj.core.api.Assertions.assertThat;
import static sonia.scm.repository.Branch.normalBranch;
class BranchTest {
@Test
void shouldTagOldBranchAsStale() {
long moreThanTwoWeeksAgo =
now()
.minus(14, ChronoUnit.DAYS)
.minus(1, ChronoUnit.MINUTES)
.toEpochMilli();
Branch branch = normalBranch("hog", "42", moreThanTwoWeeksAgo);
assertThat(branch.isStale()).isTrue();
}
@Test
void shouldNotTagNotSoOldBranchAsStale() {
long moreThanTwoWeeksAgo =
now()
.minus(14, ChronoUnit.DAYS)
.plus(1, ChronoUnit.MINUTES)
.toEpochMilli();
Branch branch = normalBranch("hog", "42", moreThanTwoWeeksAgo);
assertThat(branch.isStale()).isFalse();
}
}

View File

@@ -24,14 +24,14 @@
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import { BranchSelector } from "./index"; import { BranchSelector } from "./index";
import { Branch } from "@scm-manager/ui-types/src"; import { Branch } from "@scm-manager/ui-types";
import * as React from "react"; import * as React from "react";
import styled from "styled-components"; import styled from "styled-components";
const master = { name: "master", revision: "1", defaultBranch: true, _links: {} }; const master = { name: "master", revision: "1", defaultBranch: true, _links: {} };
const develop = { name: "develop", revision: "2", defaultBranch: false, _links: {} }; const develop = { name: "develop", revision: "2", defaultBranch: false, _links: {} };
const branchSelected = (branch?: Branch) => {}; const branchSelected = (branch?: Branch) => null;
const branches = [master, develop]; const branches = [master, develop];
@@ -42,6 +42,4 @@ const Wrapper = styled.div`
storiesOf("BranchSelector", module) storiesOf("BranchSelector", module)
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>) .addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>)
.add("Default", () => ( .add("Default", () => <BranchSelector branches={branches} onSelectBranch={branchSelected} label="Select branch:" />);
<BranchSelector branches={branches} onSelectBranch={branchSelected} label="Select branch:" />
));

View File

@@ -29,6 +29,7 @@ export type Branch = {
revision: string; revision: string;
defaultBranch?: boolean; defaultBranch?: boolean;
lastCommitDate?: string; lastCommitDate?: string;
stale?: boolean;
_links: Links; _links: Links;
}; };

View File

@@ -64,7 +64,10 @@
"createButton": "Branch erstellen" "createButton": "Branch erstellen"
}, },
"table": { "table": {
"branches": "Branches", "branches": {
"active": "Aktive Branches",
"stale": "Alte Branches"
},
"lastCommit": "Letzter Commit" "lastCommit": "Letzter Commit"
}, },
"create": { "create": {

View File

@@ -64,7 +64,10 @@
"createButton": "Create Branch" "createButton": "Create Branch"
}, },
"table": { "table": {
"branches": "Branches", "branches": {
"active": "Active Branches",
"stale": "Stale Branches"
},
"lastCommit": "Last commit" "lastCommit": "Last commit"
}, },
"create": { "create": {

View File

@@ -30,10 +30,11 @@ import { apiClient, ConfirmAlert, ErrorNotification } from "@scm-manager/ui-comp
type Props = { type Props = {
baseUrl: string; baseUrl: string;
branches: Branch[]; branches: Branch[];
type: string;
fetchBranches: () => void; fetchBranches: () => void;
}; };
const BranchTable: FC<Props> = ({ baseUrl, branches, fetchBranches }) => { const BranchTable: FC<Props> = ({ baseUrl, branches, type, fetchBranches }) => {
const [t] = useTranslation("repos"); const [t] = useTranslation("repos");
const [showConfirmAlert, setShowConfirmAlert] = useState(false); const [showConfirmAlert, setShowConfirmAlert] = useState(false);
const [error, setError] = useState<Error | undefined>(); const [error, setError] = useState<Error | undefined>();
@@ -92,7 +93,7 @@ const BranchTable: FC<Props> = ({ baseUrl, branches, fetchBranches }) => {
<table className="card-table table is-hoverable is-fullwidth is-word-break"> <table className="card-table table is-hoverable is-fullwidth is-word-break">
<thead> <thead>
<tr> <tr>
<th>{t("branches.table.branches")}</th> <th>{t(`branches.table.branches.${type}`)}</th>
</tr> </tr>
</thead> </thead>
<tbody>{renderRow()}</tbody> <tbody>{renderRow()}</tbody>

View File

@@ -84,7 +84,28 @@ class BranchesOverview extends React.Component<Props> {
const { baseUrl, branches, repository, fetchBranches, t } = this.props; const { baseUrl, branches, repository, fetchBranches, t } = this.props;
if (branches && branches.length > 0) { if (branches && branches.length > 0) {
orderBranches(branches); orderBranches(branches);
return <BranchTable baseUrl={baseUrl} branches={branches} fetchBranches={() => fetchBranches(repository)} />; const staleBranches = branches.filter(b => b.stale);
const activeBranches = branches.filter(b => !b.stale);
return (
<>
{activeBranches.length > 0 && (
<BranchTable
baseUrl={baseUrl}
type={"active"}
branches={activeBranches}
fetchBranches={() => fetchBranches(repository)}
/>
)}
{staleBranches.length > 0 && (
<BranchTable
baseUrl={baseUrl}
type={"stale"}
branches={staleBranches}
fetchBranches={() => fetchBranches(repository)}
/>
)}
</>
);
} }
return <Notification type="info">{t("branches.overview.noBranches")}</Notification>; return <Notification type="info">{t("branches.overview.noBranches")}</Notification>;
} }

View File

@@ -55,6 +55,7 @@ public class BranchDto extends HalRepresentation {
private boolean defaultBranch; private boolean defaultBranch;
@JsonInclude(NON_NULL) @JsonInclude(NON_NULL)
private Instant lastCommitDate; private Instant lastCommitDate;
private boolean stale;
BranchDto(Links links, Embedded embedded) { BranchDto(Links links, Embedded embedded) {
super(links, embedded); super(links, embedded);