mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
show modified information on branches overview
This commit is contained in:
@@ -23,11 +23,14 @@
|
||||
*/
|
||||
|
||||
import { Links } from "./hal";
|
||||
import { Person } from ".";
|
||||
|
||||
export type Branch = {
|
||||
name: string;
|
||||
revision: string;
|
||||
defaultBranch?: boolean;
|
||||
lastModified?: Date;
|
||||
lastModifier?: Person;
|
||||
_links: Links;
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,9 @@
|
||||
"overview": {
|
||||
"title": "Übersicht aller verfügbaren Branches",
|
||||
"noBranches": "Keine Branches gefunden.",
|
||||
"createButton": "Branch erstellen"
|
||||
"createButton": "Branch erstellen",
|
||||
"lastModifier": "von",
|
||||
"lastModified": "Aktualisiert"
|
||||
},
|
||||
"table": {
|
||||
"branches": "Branches"
|
||||
|
||||
@@ -51,7 +51,9 @@
|
||||
"overview": {
|
||||
"title": "Overview of all branches",
|
||||
"noBranches": "No branches found.",
|
||||
"createButton": "Create Branch"
|
||||
"createButton": "Create Branch",
|
||||
"lastModifier": "by",
|
||||
"lastModified": "Updated"
|
||||
},
|
||||
"table": {
|
||||
"branches": "Branches"
|
||||
|
||||
@@ -21,34 +21,42 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Branch } from "@scm-manager/ui-types";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string;
|
||||
branch: Branch;
|
||||
};
|
||||
|
||||
class BranchRow extends React.Component<Props> {
|
||||
renderLink(to: string, label: string, defaultBranch?: boolean) {
|
||||
return (
|
||||
<Link to={to} title={label}>
|
||||
{label} <DefaultBranchTag defaultBranch={defaultBranch} />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
const Modified = styled.span`
|
||||
margin-left: 1rem;
|
||||
font-size: 0.8rem;
|
||||
`;
|
||||
|
||||
const BranchRow: FC<Props> = ({ baseUrl, branch }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
|
||||
render() {
|
||||
const { baseUrl, branch } = this.props;
|
||||
const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`;
|
||||
return (
|
||||
<tr>
|
||||
<td>{this.renderLink(to, branch.name, branch.defaultBranch)}</td>
|
||||
<td>
|
||||
<Link to={to} title={branch.name}>
|
||||
{branch.name}
|
||||
<DefaultBranchTag defaultBranch={branch.defaultBranch} />
|
||||
<Modified className="has-text-grey is-ellipsis-overflow">
|
||||
{t("branches.overview.lastModified")} <DateFromNow date={branch.lastModified} />{" "}
|
||||
{t("branches.overview.lastModifier")} {branch.lastModifier?.name}
|
||||
</Modified>
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default BranchRow;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
@@ -31,20 +32,29 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.time.Instant;
|
||||
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class BranchDto extends HalRepresentation {
|
||||
|
||||
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
||||
static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
|
||||
|
||||
@NotEmpty @Length(min = 1, max=100) @Pattern(regexp = VALID_BRANCH_NAMES)
|
||||
@NotEmpty
|
||||
@Length(min = 1, max = 100)
|
||||
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||
private String name;
|
||||
private String revision;
|
||||
private boolean defaultBranch;
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private Instant lastModified;
|
||||
private PersonDto lastModifier;
|
||||
|
||||
BranchDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
|
||||
@@ -30,11 +30,19 @@ import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
import sonia.scm.web.EdisonHalAppender;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import static de.otto.edison.hal.Link.linkBuilder;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
@@ -45,9 +53,14 @@ public abstract class BranchToBranchDtoMapper extends HalAppenderMapper {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
@Inject
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract BranchDto map(Branch branch, @Context NamespaceAndName namespaceAndName);
|
||||
|
||||
abstract PersonDto map(Person person);
|
||||
|
||||
@ObjectFactory
|
||||
BranchDto createDto(@Context NamespaceAndName namespaceAndName, Branch branch) {
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
@@ -58,7 +71,20 @@ public abstract class BranchToBranchDtoMapper extends HalAppenderMapper {
|
||||
|
||||
Embedded.Builder embeddedBuilder = Embedded.embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), branch, namespaceAndName);
|
||||
BranchDto branchDto = new BranchDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
|
||||
return new BranchDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
try (RepositoryService service = serviceFactory.create(namespaceAndName)) {
|
||||
Changeset latestChangeset = service.getLogCommand().setBranch(branch.getName()).getChangesets().getChangesets().get(0);
|
||||
branchDto.setLastModified(Instant.ofEpochMilli(latestChangeset.getDate()));
|
||||
branchDto.setLastModifier(map(latestChangeset.getAuthor()));
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(
|
||||
ContextEntry.ContextBuilder.entity(Branch.class, branch.getName()),
|
||||
"Could not read latest changeset for branch",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
return branchDto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,16 +24,29 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.PersonTestData;
|
||||
import sonia.scm.repository.api.LogCommandBuilder;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class BranchToBranchDtoMapperTest {
|
||||
@@ -43,6 +56,13 @@ class BranchToBranchDtoMapperTest {
|
||||
@SuppressWarnings("unused") // Is injected
|
||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||
|
||||
@Mock
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@Mock
|
||||
private RepositoryService repositoryService;
|
||||
@Mock(answer = Answers.RETURNS_SELF)
|
||||
private LogCommandBuilder logCommandBuilder;
|
||||
|
||||
@InjectMocks
|
||||
private BranchToBranchDtoMapperImpl mapper;
|
||||
|
||||
@@ -63,4 +83,21 @@ class BranchToBranchDtoMapperTest {
|
||||
assertThat(dto.getLinks().getLinkBy("ka").get().getHref()).isEqualTo("http://hitchhiker/heart-of-gold/master");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapLastChangeDateAndLastModifier() throws IOException {
|
||||
long creationTime = 1000000000;
|
||||
Changeset changeset = new Changeset("1", 1L, PersonTestData.ZAPHOD);
|
||||
changeset.setDate(creationTime);
|
||||
|
||||
when(serviceFactory.create(any(NamespaceAndName.class))).thenReturn(repositoryService);
|
||||
when(repositoryService.getLogCommand()).thenReturn(logCommandBuilder);
|
||||
when(logCommandBuilder.getChangesets()).thenReturn(new ChangesetPagingResult(1, ImmutableList.of(changeset)));
|
||||
Branch branch = Branch.normalBranch("master", "42");
|
||||
|
||||
BranchDto dto = mapper.map(branch, new NamespaceAndName("hitchhiker", "heart-of-gold"));
|
||||
|
||||
assertThat(dto.getLastModified()).isEqualTo(Instant.ofEpochMilli(creationTime));
|
||||
assertThat(dto.getLastModifier().getName()).isEqualTo(PersonTestData.ZAPHOD.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user