From e3170543cba4a29691aa2850e619edcbe205dee8 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Wed, 25 Nov 2020 15:51:44 +0100 Subject: [PATCH] add frontend for deleting tags --- scm-ui/ui-webapp/public/locales/de/repos.json | 13 ++++- scm-ui/ui-webapp/public/locales/en/repos.json | 13 ++++- .../src/repos/tags/components/TagRow.tsx | 25 ++++++--- .../src/repos/tags/components/TagTable.tsx | 54 +++++++++++++++++-- .../src/repos/tags/container/TagsOverview.tsx | 8 ++- .../scm/api/v2/resources/ResourceLinks.java | 4 ++ .../api/v2/resources/TagToTagDtoMapper.java | 3 +- 7 files changed, 104 insertions(+), 16 deletions(-) diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index d95ef44970..a0b6ef015b 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -95,7 +95,18 @@ "tag": { "name": "Name", "commit": "Commit", - "sources": "Sources" + "sources": "Sources", + "delete": { + "button": "Tag löschen", + "subtitle": "Tag löschen", + "description": "Gelöschte Tags können nicht wiederhergestellt werden.", + "confirmAlert": { + "title": "Tag löschen", + "message": "Möchten Sie den Tag \"{{tag}}\" wirklich löschen?", + "cancel": "Nein", + "submit": "Ja" + } + } }, "code": { "sources": "Sources", diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 59981282df..f2f6f31cea 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -95,7 +95,18 @@ "tag": { "name": "Name", "commit": "Commit", - "sources": "Sources" + "sources": "Sources", + "delete": { + "button": "Delete tag", + "subtitle": "Delete tag", + "description": "Deleted tag can not be restored.", + "confirmAlert": { + "title": "Delete tag", + "message": "Do you really want to delete the tag \"{{tag}}\"?", + "cancel": "No", + "submit": "Yes" + } + } }, "code": { "sources": "Sources", diff --git a/scm-ui/ui-webapp/src/repos/tags/components/TagRow.tsx b/scm-ui/ui-webapp/src/repos/tags/components/TagRow.tsx index 00b18da623..9b45816231 100644 --- a/scm-ui/ui-webapp/src/repos/tags/components/TagRow.tsx +++ b/scm-ui/ui-webapp/src/repos/tags/components/TagRow.tsx @@ -24,14 +24,15 @@ import React, { FC } from "react"; import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; -import { Tag } from "@scm-manager/ui-types"; +import { Link as RouterLink } from "react-router-dom"; +import { Tag, Link } from "@scm-manager/ui-types"; import styled from "styled-components"; -import { DateFromNow } from "@scm-manager/ui-components"; +import { DateFromNow, Icon } from "@scm-manager/ui-components"; type Props = { tag: Tag; baseUrl: string; + onDelete: (tag: Tag) => void; }; const Created = styled.span` @@ -39,20 +40,32 @@ const Created = styled.span` font-size: 0.8rem; `; -const TagRow: FC = ({ tag, baseUrl }) => { +const TagRow: FC = ({ tag, baseUrl, onDelete }) => { const [t] = useTranslation("repos"); + let deleteButton; + if ((tag?._links?.delete as Link)?.href) { + deleteButton = ( + onDelete(tag)}> + + + + + ); + } + const to = `${baseUrl}/${encodeURIComponent(tag.name)}/info`; return ( - + {tag.name} {t("tags.overview.created")} - + + {deleteButton} ); }; diff --git a/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx b/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx index 4cd694766f..5778b346a9 100644 --- a/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx +++ b/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx @@ -22,30 +22,74 @@ * SOFTWARE. */ -import React, { FC } from "react"; -import { Tag } from "@scm-manager/ui-types"; +import React, {FC, useState} from "react"; +import {Link, Tag} from "@scm-manager/ui-types"; import { useTranslation } from "react-i18next"; import TagRow from "./TagRow"; +import {apiClient, ConfirmAlert, ErrorNotification} from "@scm-manager/ui-components"; type Props = { baseUrl: string; tags: Tag[]; + fetchTags: () => void; }; -const TagTable: FC = ({ baseUrl, tags }) => { +const TagTable: FC = ({ baseUrl, tags, fetchTags }) => { const [t] = useTranslation("repos"); + const [showConfirmAlert, setShowConfirmAlert] = useState(false); + const [error, setError] = useState(); + const [tagToBeDeleted, setTagToBeDeleted] = useState(); + + const onDelete = (tag: Tag) => { + setTagToBeDeleted(tag); + setShowConfirmAlert(true); + }; + + const abortDelete = () => { + setTagToBeDeleted(undefined); + setShowConfirmAlert(false); + }; + + const deleteTag = () => { + apiClient + .delete((tagToBeDeleted?._links.delete as Link).href) + .then(() => fetchTags()) + .catch(setError); + }; const renderRow = () => { let rowContent = null; if (tags) { rowContent = tags.map((tag, index) => { - return ; + return ; }); } return rowContent; }; + const confirmAlert = ( + deleteTag() + }, + { + label: t("tag.delete.confirmAlert.cancel"), + onClick: () => abortDelete() + } + ]} + close={() => abortDelete()} + /> + ); + return ( + <> + {showConfirmAlert && confirmAlert} + {error && } @@ -54,7 +98,7 @@ const TagTable: FC = ({ baseUrl, tags }) => { {renderRow()}
- ); + ); }; export default TagTable; diff --git a/scm-ui/ui-webapp/src/repos/tags/container/TagsOverview.tsx b/scm-ui/ui-webapp/src/repos/tags/container/TagsOverview.tsx index 42de372b60..e449358c3b 100644 --- a/scm-ui/ui-webapp/src/repos/tags/container/TagsOverview.tsx +++ b/scm-ui/ui-webapp/src/repos/tags/container/TagsOverview.tsx @@ -40,7 +40,7 @@ const TagsOverview: FC = ({ repository, baseUrl }) => { const [error, setError] = useState(undefined); const [tags, setTags] = useState([]); - useEffect(() => { + const fetchTags = () => { const link = (repository._links?.tags as Link)?.href; if (link) { setLoading(true); @@ -51,12 +51,16 @@ const TagsOverview: FC = ({ repository, baseUrl }) => { .then(() => setLoading(false)) .catch(setError); } + }; + + useEffect(() => { + fetchTags(); }, [repository]); const renderTagsTable = () => { if (!loading && tags?.length > 0) { orderTags(tags); - return ; + return fetchTags()} />; } return {t("tags.overview.noTags")}; }; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 7bd6c353f5..f24490eef1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -445,6 +445,10 @@ class ResourceLinks { return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("get").parameters(tagName).href(); } + String delete(String namespace, String name, String tagName) { + return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("delete").parameters(tagName).href(); + } + String all(String namespace, String name) { return tagLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("tags").parameters().method("getAll").parameters().href(); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index 78350ab114..cfa0c2db8c 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -60,7 +60,8 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper { Links.Builder linksBuilder = linkingTo() .self(resourceLinks.tag().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName())) .single(link("sources", resourceLinks.source().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision()))) - .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision()))); + .single(link("changeset", resourceLinks.changeset().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getRevision()))) + .single(link("delete", resourceLinks.tag().delete(namespaceAndName.getNamespace(), namespaceAndName.getName(), tag.getName()))); Embedded.Builder embeddedBuilder = embeddedBuilder(); applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), tag, namespaceAndName);