implement frontend for creating tags

This commit is contained in:
Konstantin Schaper
2020-11-27 08:49:10 +01:00
parent 280f3e005e
commit 7895da0a2a
7 changed files with 203 additions and 108 deletions

View File

@@ -94,9 +94,11 @@ class Button extends React.Component<Props> {
<span className="icon is-medium">
<Icon name={icon} color="inherit" />
</span>
{(label || children) && (
<span>
{label} {children}
</span>
)}
</button>
);
}

View File

@@ -90,6 +90,19 @@
},
"table": {
"tags": "Tags"
},
"create": {
"form": {
"field": {
"name": {
"label": "Name"
}
}
},
"title": "Neuen Tag anlegen",
"hint": "Der Tag wird automatisch mit ihrem Standardschlüssel vom SCM-Manager signiert.",
"confirm": "Tag erstellen",
"cancel": "Abbrechen"
}
},
"tag": {
@@ -156,6 +169,9 @@
"buttons": {
"details": "Details",
"sources": "Sources"
},
"tag": {
"create": "Tag erstellen"
}
},
"commit": {

View File

@@ -90,6 +90,19 @@
},
"table": {
"tags": "Tags"
},
"create": {
"form": {
"field": {
"name": {
"label": "Name"
}
}
},
"title": "Create a new tag",
"hint": "The tag will be automatically signed with your default key by the SCM-Manager.",
"confirm": "Create Tag",
"cancel": "Cancel"
}
},
"tag": {
@@ -156,6 +169,9 @@
"more": "{{count}} more",
"count": "{{count}} Contributor",
"count_plural": "{{count}} Contributors"
},
"tag": {
"create": "Create Tag"
}
},
"commit": {

View File

@@ -26,7 +26,7 @@ import { Trans, useTranslation, WithTranslation, withTranslation } from "react-i
import classNames from "classnames";
import styled from "styled-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Changeset, ParentChangeset, Repository } from "@scm-manager/ui-types";
import { Changeset, Link, ParentChangeset, Repository } from "@scm-manager/ui-types";
import {
AvatarImage,
AvatarWrapper,
@@ -41,7 +41,10 @@ import {
FileControlFactory,
Icon,
Level,
SignatureIcon
SignatureIcon,
Modal,
InputField,
apiClient
} from "@scm-manager/ui-components";
import ContributorTable from "./ContributorTable";
import { Link as ReactLink } from "react-router-dom";
@@ -50,6 +53,7 @@ type Props = WithTranslation & {
changeset: Changeset;
repository: Repository;
fileControlFactory?: FileControlFactory;
refetchChangeset?: (re) => void;
};
type State = {
@@ -82,11 +86,12 @@ const ContributorLine = styled.div`
`;
const ContributorColumn = styled.p`
flex-grow: 1;
flex-grow: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
margin-right: 16px;
`;
const CountColumn = styled.p`
@@ -159,17 +164,10 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => {
);
};
class ChangesetDetails extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
collapsed: false
};
}
render() {
const { changeset, repository, fileControlFactory, t } = this.props;
const { collapsed } = this.state;
const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory, t , refetchChangeset}) => {
const [collapsed, setCollapsed] = useState(false);
const [isTagCreationModalVisible, setTagCreationModalVisible] = useState(false);
const [newTagName, setNewTagName] = useState("");
const description = changesets.parseDescription(changeset.description);
const id = <ChangesetId repository={repository} changeset={changeset} link={false} />;
@@ -179,6 +177,28 @@ class ChangesetDetails extends React.Component<Props, State> {
{parent.id.substring(0, 7)}
</ReactLink>
));
const showCreateButton = "tag" in changeset._links;
const collapseDiffs = () => {
setCollapsed(!collapsed);
};
const createTag = () => {
apiClient
.post((changeset._links["tag"] as Link).href, {
revision: changeset.id,
name: newTagName
})
.then(() => {
refetchChangeset?.();
closeTagCreationModal();
});
};
const closeTagCreationModal = () => {
setNewTagName("");
setTagCreationModalVisible(false);
};
return (
<>
@@ -218,6 +238,43 @@ class ChangesetDetails extends React.Component<Props, State> {
<div className="media-right">
<ChangesetTags changeset={changeset} />
</div>
<div className="media-right">
{showCreateButton && (
<Button
color="success"
className="tag"
label={(changeset._embedded["tags"]?.length <= 1 && t("changeset.tag.create")) || ""}
icon="plus"
action={() => setTagCreationModalVisible(true)}
/>
)}
{isTagCreationModalVisible && (
<Modal
title={t("tags.create.title")}
active={true}
body={
<>
<InputField
name="name"
label={t("tags.create.form.field.name.label")}
onChange={val => setNewTagName(val)}
value={newTagName}
/>
<div>{t("tags.create.hint")}</div>
</>
}
footer={
<>
<Button action={() => closeTagCreationModal()}>{t("tags.create.cancel")}</Button>
<Button color="success" action={() => createTag()}>
{t("tags.create.confirm")}
</Button>
</>
}
closeFunction={() => closeTagCreationModal()}
/>
)}
</div>
</article>
<p>
{description.message.split("\n").map((item, key) => {
@@ -243,7 +300,7 @@ class ChangesetDetails extends React.Component<Props, State> {
<BottomMarginLevel
right={
<Button
action={this.collapseDiffs}
action={collapseDiffs}
color="default"
icon={collapsed ? "eye" : "eye-slash"}
label={t("changesets.collapseDiffs")}
@@ -255,13 +312,6 @@ class ChangesetDetails extends React.Component<Props, State> {
</div>
</>
);
}
collapseDiffs = () => {
this.setState(state => ({
collapsed: !state.collapsed
}));
};
}
};
export default withTranslation("repos")(ChangesetDetails);

View File

@@ -29,6 +29,7 @@ import { WithTranslation, withTranslation } from "react-i18next";
import { Changeset, Repository } from "@scm-manager/ui-types";
import { ErrorPage, Loading } from "@scm-manager/ui-components";
import {
fetchChangeset,
fetchChangesetIfNeeded,
getChangeset,
getFetchChangesetFailure,
@@ -45,6 +46,7 @@ type Props = WithTranslation & {
loading: boolean;
error: Error;
fetchChangesetIfNeeded: (repository: Repository, id: string) => void;
refetchChangeset: (repository: Repository, id: string) => void;
match: any;
};
@@ -62,7 +64,7 @@ class ChangesetView extends React.Component<Props> {
}
render() {
const { changeset, loading, error, t, repository, fileControlFactoryFactory } = this.props;
const { changeset, loading, error, t, repository, fileControlFactoryFactory, refetchChangeset } = this.props;
if (error) {
return <ErrorPage title={t("changesets.errorTitle")} subtitle={t("changesets.errorSubtitle")} error={error} />;
@@ -75,6 +77,7 @@ class ChangesetView extends React.Component<Props> {
changeset={changeset}
repository={repository}
fileControlFactory={fileControlFactoryFactory && fileControlFactoryFactory(changeset)}
refetchChangeset={() => refetchChangeset(repository, changeset.id)}
/>
);
}
@@ -98,6 +101,9 @@ const mapDispatchToProps = (dispatch: any) => {
return {
fetchChangesetIfNeeded: (repository: Repository, id: string) => {
dispatch(fetchChangesetIfNeeded(repository, id));
},
refetchChangeset: (repository: Repository, id: string) => {
dispatch(fetchChangeset(repository, id));
}
};
};

View File

@@ -130,6 +130,7 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
getListOfObjects(source.getTags(), tags::getTagByName)));
}
linksBuilder.single(link("tag", resourceLinks.tag().create(namespace, name)));
}
if (repositoryService.isSupported(Command.BRANCHES)) {
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(repository,

View File

@@ -449,6 +449,10 @@ class ResourceLinks {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("delete").parameters(tagName).href();
}
String create(String namespace, String name) {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("create").parameters().href();
}
String all(String namespace, String name) {
return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("getAll").parameters().href();
}