From ab2bd6d6790cf61db6a885a4d8ec5b9fe207a59d Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 16 Jun 2021 14:45:40 +0200 Subject: [PATCH] Fix single tag deletion (#1700) Redirect to tags overview after a single tag was deleted to prevent error page. Co-authored-by: Konstantin Schaper --- .../changelog/delete_tag_with_redirect.yaml | 2 ++ scm-ui/ui-api/src/tags.test.ts | 12 +++++++-- scm-ui/ui-api/src/tags.ts | 27 ++++++++++--------- .../src/repos/tags/components/TagTable.tsx | 12 ++++----- 4 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 gradle/changelog/delete_tag_with_redirect.yaml diff --git a/gradle/changelog/delete_tag_with_redirect.yaml b/gradle/changelog/delete_tag_with_redirect.yaml new file mode 100644 index 0000000000..dc423b352e --- /dev/null +++ b/gradle/changelog/delete_tag_with_redirect.yaml @@ -0,0 +1,2 @@ +- type: fixed + description: Redirect after single tag was deleted ([#1700](https://github.com/scm-manager/scm-manager/pull/1700)) diff --git a/scm-ui/ui-api/src/tags.test.ts b/scm-ui/ui-api/src/tags.test.ts index dee83bf46c..e027eb73e3 100644 --- a/scm-ui/ui-api/src/tags.test.ts +++ b/scm-ui/ui-api/src/tags.test.ts @@ -239,14 +239,22 @@ describe("Test Tag hooks", () => { expect(queryState!.isInvalidated).toBe(true); }; + const shouldRemoveQuery = async (queryKey: string[], data: unknown) => { + queryClient.setQueryData(queryKey, data); + await deleteTag(); + + const queryState = queryClient.getQueryState(queryKey); + expect(queryState).toBeUndefined(); + }; + it("should delete tag", async () => { const { isDeleted } = await deleteTag(); expect(isDeleted).toBe(true); }); - it("should invalidate tag cache", async () => { - await shouldInvalidateQuery(["repository", "hitchhiker", "heart-of-gold", "tag", "1.0"], tagOneDotZero); + it("should delete tag cache", async () => { + await shouldRemoveQuery(["repository", "hitchhiker", "heart-of-gold", "tag", "1.0"], tagOneDotZero); }); it("should invalidate tag collection cache", async () => { diff --git a/scm-ui/ui-api/src/tags.ts b/scm-ui/ui-api/src/tags.ts index 9a276b4208..fe722dbcdd 100644 --- a/scm-ui/ui-api/src/tags.ts +++ b/scm-ui/ui-api/src/tags.ts @@ -37,7 +37,7 @@ export const useTags = (repository: Repository): ApiResult => { const link = requiredLink(repository, "tags"); return useQuery( repoQueryKey(repository, "tags"), - () => apiClient.get(link).then(response => response.json()) + () => apiClient.get(link).then((response) => response.json()) // we do not populate the cache for a single tag, // because we have no pagination for tags and if we have a lot of them // the population slows us down @@ -47,16 +47,15 @@ export const useTags = (repository: Repository): ApiResult => { export const useTag = (repository: Repository, name: string): ApiResult => { const link = requiredLink(repository, "tags"); return useQuery(tagQueryKey(repository, name), () => - apiClient.get(concat(link, name)).then(response => response.json()) + apiClient.get(concat(link, name)).then((response) => response.json()) ); }; const invalidateCacheForTag = (queryClient: QueryClient, repository: NamespaceAndName, tag: Tag) => { return Promise.all([ queryClient.invalidateQueries(repoQueryKey(repository, "tags")), - queryClient.invalidateQueries(tagQueryKey(repository, tag.name)), queryClient.invalidateQueries(repoQueryKey(repository, "changesets")), - queryClient.invalidateQueries(repoQueryKey(repository, "changeset", tag.revision)) + queryClient.invalidateQueries(repoQueryKey(repository, "changeset", tag.revision)), ]); }; @@ -65,16 +64,16 @@ const createTag = (changeset: Changeset, link: string) => { return apiClient .post(link, { name, - revision: changeset.id + revision: changeset.id, }) - .then(response => { + .then((response) => { const location = response.headers.get("Location"); if (!location) { throw new Error("Server does not return required Location header"); } return apiClient.get(location); }) - .then(response => response.json()); + .then((response) => response.json()); }; }; @@ -82,36 +81,38 @@ export const useCreateTag = (repository: Repository, changeset: Changeset) => { const queryClient = useQueryClient(); const link = requiredLink(changeset, "tag"); const { isLoading, error, mutate, data } = useMutation(createTag(changeset, link), { - onSuccess: async tag => { + onSuccess: async (tag) => { queryClient.setQueryData(tagQueryKey(repository, tag.name), tag); + await queryClient.invalidateQueries(tagQueryKey(repository, tag.name)); await invalidateCacheForTag(queryClient, repository, tag); - } + }, }); return { isLoading, error, create: (name: string) => mutate(name), - tag: data + tag: data, }; }; export const useDeleteTag = (repository: Repository) => { const queryClient = useQueryClient(); const { mutate, isLoading, error, data } = useMutation( - tag => { + (tag) => { const deleteUrl = (tag._links.delete as Link).href; return apiClient.delete(deleteUrl); }, { onSuccess: async (_, tag) => { + queryClient.removeQueries(tagQueryKey(repository, tag.name)); await invalidateCacheForTag(queryClient, repository, tag); - } + }, } ); return { remove: (tag: Tag) => mutate(tag), isLoading, error, - isDeleted: !!data + isDeleted: !!data, }; }; 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 8fad7513bc..6baa00516d 100644 --- a/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx +++ b/scm-ui/ui-webapp/src/repos/tags/components/TagTable.tsx @@ -23,10 +23,10 @@ */ import React, { FC, useEffect, useState } from "react"; -import { Link, Repository, Tag } from "@scm-manager/ui-types"; +import { Repository, Tag } from "@scm-manager/ui-types"; import { useTranslation } from "react-i18next"; import TagRow from "./TagRow"; -import { apiClient, ConfirmAlert, ErrorNotification } from "@scm-manager/ui-components"; +import { ConfirmAlert, ErrorNotification } from "@scm-manager/ui-components"; import { useDeleteTag } from "@scm-manager/ui-api"; type Props = { @@ -77,12 +77,12 @@ const TagTable: FC = ({ repository, baseUrl, tags }) => { className: "is-outlined", label: t("tag.delete.confirmAlert.submit"), isLoading, - onClick: () => deleteTag() + onClick: () => deleteTag(), }, { label: t("tag.delete.confirmAlert.cancel"), - onClick: () => abortDelete() - } + onClick: () => abortDelete(), + }, ]} close={() => abortDelete()} /> @@ -95,7 +95,7 @@ const TagTable: FC = ({ repository, baseUrl, tags }) => { - {tags.map(tag => ( + {tags.map((tag) => ( ))}