From 302dcda48c7465ac57c62e7d4dcb0488530fad54 Mon Sep 17 00:00:00 2001 From: minux Date: Wed, 13 Nov 2024 15:11:51 +0900 Subject: [PATCH] Provide ways to delete mirror and credential (#1051) There's no way to delete mirror and credential Result: - You can now delete mirror and credential. --- .../MirroringAndCredentialServiceV1Test.java | 38 +++++++++++ .../internal/api/CredentialServiceV1.java | 27 ++++++++ .../internal/api/MirroringServiceV1.java | 27 ++++++++ .../repository/DefaultMetaRepository.java | 2 +- webapp/src/dogma/features/api/apiSlice.ts | 16 +++++ .../settings/DeleteConfirmationModal.tsx | 54 +++++++++++++++ .../settings/credentials/CredentialList.tsx | 18 ++++- .../settings/credentials/DeleteCredential.tsx | 46 +++++++++++++ .../project/settings/members/DeleteMember.tsx | 45 ++++--------- .../project/settings/mirrors/DeleteMirror.tsx | 46 +++++++++++++ .../project/settings/mirrors/MirrorList.tsx | 67 +++++++++++-------- .../project/settings/tokens/AppTokenList.tsx | 10 +-- .../project/settings/tokens/DeleteToken.tsx | 48 +++++++++++++ .../v1/AggregatingMultipleKubernetesTest.java | 3 - 14 files changed, 374 insertions(+), 73 deletions(-) create mode 100644 webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx create mode 100644 webapp/src/dogma/features/project/settings/credentials/DeleteCredential.tsx create mode 100644 webapp/src/dogma/features/project/settings/mirrors/DeleteMirror.tsx create mode 100644 webapp/src/dogma/features/project/settings/tokens/DeleteToken.tsx diff --git a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java index e5d2b0cf73..e6e905e459 100644 --- a/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java +++ b/server-mirror-git/src/test/java/com/linecorp/centraldogma/server/internal/mirror/MirroringAndCredentialServiceV1Test.java @@ -113,6 +113,8 @@ void cruTest() { createAndReadMirror(); updateMirror(); rejectInvalidRepositoryUri(); + deleteMirror(); + deleteCredential(); } private void rejectInvalidRepositoryUri() { @@ -325,6 +327,42 @@ private void updateMirror() { assertThat(savedMirror).isEqualTo(mirror); } + private void deleteMirror() { + final String mirrorId = "mirror-2"; + assertThat(userClient.prepare() + .delete("/api/v1/projects/{proj}/mirrors/{id}") + .pathParam("proj", FOO_PROJ) + .pathParam("id", mirrorId) + .execute() + .status()) + .isEqualTo(HttpStatus.NO_CONTENT); + assertThat(userClient.prepare() + .get("/api/v1/projects/{proj}/mirrors/{id}") + .pathParam("proj", FOO_PROJ) + .pathParam("id", mirrorId) + .execute() + .status()) + .isEqualTo(HttpStatus.NOT_FOUND); + } + + private void deleteCredential() { + final String credentialId = "public-key-credential"; + assertThat(userClient.prepare() + .delete("/api/v1/projects/{proj}/credentials/{id}") + .pathParam("proj", FOO_PROJ) + .pathParam("id", credentialId) + .execute() + .status()) + .isEqualTo(HttpStatus.NO_CONTENT); + assertThat(userClient.prepare() + .get("/api/v1/projects/{proj}/credentials/{id}") + .pathParam("proj", FOO_PROJ) + .pathParam("id", credentialId) + .execute() + .status()) + .isEqualTo(HttpStatus.NOT_FOUND); + } + private static MirrorDto newMirror(String id) { return new MirrorDto(id, true, diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java index e127cfefd3..b0972be664 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java @@ -18,11 +18,13 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.credentialFile; import java.util.List; import java.util.concurrent.CompletableFuture; import com.linecorp.armeria.server.annotation.ConsumesJson; +import com.linecorp.armeria.server.annotation.Delete; import com.linecorp.armeria.server.annotation.Get; import com.linecorp.armeria.server.annotation.Param; import com.linecorp.armeria.server.annotation.Post; @@ -30,8 +32,13 @@ import com.linecorp.armeria.server.annotation.Put; import com.linecorp.armeria.server.annotation.StatusCode; import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.Change; +import com.linecorp.centraldogma.common.Markup; +import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.internal.api.v1.PushResultDto; +import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.command.CommitResult; import com.linecorp.centraldogma.server.credential.Credential; import com.linecorp.centraldogma.server.internal.api.auth.RequiresReadPermission; import com.linecorp.centraldogma.server.internal.api.auth.RequiresWritePermission; @@ -118,6 +125,26 @@ public CompletableFuture updateCredential(@Param String projectNa return createOrUpdate(projectName, credential, author, user, true); } + /** + * DELETE /projects/{projectName}/credentials/{id} + * + *

Delete the existing credential. + */ + @RequiresWritePermission(repository = Project.REPO_META) + @Delete("/projects/{projectName}/credentials/{id}") + public CompletableFuture deleteCredential(@Param String projectName, + @Param String id, Author author, User user) { + final MetaRepository metaRepository = metaRepo(projectName, user); + return metaRepository.credential(id).thenCompose(credential -> { + // credential exists. + final Command command = + Command.push(author, projectName, metaRepository.name(), + Revision.HEAD, "Delete credential: " + id, "", + Markup.PLAINTEXT, Change.ofRemoval(credentialFile(id))); + return executor().execute(command).thenApply(result -> null); + }); + } + private CompletableFuture createOrUpdate(String projectName, Credential credential, Author author, User user, boolean update) { return metaRepo(projectName, user).createPushCommand(credential, author, update).thenCompose( diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java index de866c9882..9599f50e14 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/MirroringServiceV1.java @@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.mirrorFile; import java.net.URI; import java.util.List; @@ -27,6 +28,7 @@ import com.cronutils.model.Cron; import com.linecorp.armeria.server.annotation.ConsumesJson; +import com.linecorp.armeria.server.annotation.Delete; import com.linecorp.armeria.server.annotation.Get; import com.linecorp.armeria.server.annotation.Param; import com.linecorp.armeria.server.annotation.Post; @@ -35,9 +37,14 @@ import com.linecorp.armeria.server.annotation.StatusCode; import com.linecorp.armeria.server.annotation.decorator.RequestTimeout; import com.linecorp.centraldogma.common.Author; +import com.linecorp.centraldogma.common.Change; +import com.linecorp.centraldogma.common.Markup; +import com.linecorp.centraldogma.common.Revision; import com.linecorp.centraldogma.internal.api.v1.MirrorDto; import com.linecorp.centraldogma.internal.api.v1.PushResultDto; +import com.linecorp.centraldogma.server.command.Command; import com.linecorp.centraldogma.server.command.CommandExecutor; +import com.linecorp.centraldogma.server.command.CommitResult; import com.linecorp.centraldogma.server.internal.api.auth.RequiresReadPermission; import com.linecorp.centraldogma.server.internal.api.auth.RequiresWritePermission; import com.linecorp.centraldogma.server.internal.mirror.MirrorRunner; @@ -124,6 +131,26 @@ public CompletableFuture updateMirror(@Param String projectName, return createOrUpdate(projectName, mirror, author, true); } + /** + * DELETE /projects/{projectName}/mirrors/{id} + * + *

Delete the existing mirror. + */ + @RequiresWritePermission(repository = Project.REPO_META) + @Delete("/projects/{projectName}/mirrors/{id}") + public CompletableFuture deleteMirror(@Param String projectName, + @Param String id, Author author) { + final MetaRepository metaRepository = metaRepo(projectName); + return metaRepository.mirror(id).thenCompose(mirror -> { + // mirror exists. + final Command command = + Command.push(author, projectName, metaRepository.name(), + Revision.HEAD, "Delete mirror: " + id, "", + Markup.PLAINTEXT, Change.ofRemoval(mirrorFile(id))); + return executor().execute(command).thenApply(result -> null); + }); + } + private CompletableFuture createOrUpdate(String projectName, MirrorDto newMirror, Author author, boolean update) { diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java index dfc0c5e5ed..6480792931 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java @@ -102,7 +102,7 @@ public CompletableFuture mirror(String id) { @SuppressWarnings("unchecked") final Entry entry = (Entry) entries.get(mirrorFile); if (entry == null) { - throw new EntryNotFoundException("failed to find credential '" + mirrorFile + "' in " + + throw new EntryNotFoundException("failed to find mirror '" + mirrorFile + "' in " + parent().name() + '/' + name()); } diff --git a/webapp/src/dogma/features/api/apiSlice.ts b/webapp/src/dogma/features/api/apiSlice.ts index 49d3a3b56d..56ad6328fe 100644 --- a/webapp/src/dogma/features/api/apiSlice.ts +++ b/webapp/src/dogma/features/api/apiSlice.ts @@ -347,6 +347,13 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), + deleteMirror: builder.mutation({ + query: ({ projectName, id }) => ({ + url: `/api/v1/projects/${projectName}/mirrors/${id}`, + method: 'DELETE', + }), + invalidatesTags: ['Metadata'], + }), runMirror: builder.mutation({ query: ({ projectName, id }) => ({ url: `/api/v1/projects/${projectName}/mirrors/${id}/run`, @@ -380,6 +387,13 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), + deleteCredential: builder.mutation({ + query: ({ projectName, id }) => ({ + url: `/api/v1/projects/${projectName}/credentials/${id}`, + method: 'DELETE', + }), + invalidatesTags: ['Metadata'], + }), getTitle: builder.query({ query: () => ({ baseUrl: '', @@ -430,12 +444,14 @@ export const { useGetMirrorQuery, useAddNewMirrorMutation, useUpdateMirrorMutation, + useDeleteMirrorMutation, useRunMirrorMutation, // Credential useGetCredentialsQuery, useGetCredentialQuery, useAddNewCredentialMutation, useUpdateCredentialMutation, + useDeleteCredentialMutation, // Title useGetTitleQuery, } = apiSlice; diff --git a/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx b/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx new file mode 100644 index 0000000000..32e25605d0 --- /dev/null +++ b/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx @@ -0,0 +1,54 @@ +import { + Button, + HStack, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react'; + +interface DeleteConfirmationModalProps { + isOpen: boolean; + onClose: () => void; + type: string; + id: string; + projectName: string; + handleDelete: () => void; + isLoading: boolean; +} + +export const DeleteConfirmationModal = ({ + isOpen, + onClose, + id, + type, + projectName, + handleDelete, + isLoading, +}: DeleteConfirmationModalProps): JSX.Element => { + return ( + + + + Are you sure? + + + Delete {type} '{id}' from {projectName}? + + + + + + + + + + ); +}; diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx b/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx index 3f1f0857b1..5cc188a761 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx @@ -1,10 +1,11 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; import React, { useMemo } from 'react'; import { DataTableClientPagination } from 'dogma/common/components/table/DataTableClientPagination'; -import { useGetCredentialsQuery } from 'dogma/features/api/apiSlice'; +import { useGetCredentialsQuery, useDeleteCredentialMutation } from 'dogma/features/api/apiSlice'; import { Badge } from '@chakra-ui/react'; import { ChakraLink } from 'dogma/common/components/ChakraLink'; import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; +import { DeleteCredential } from 'dogma/features/project/settings/credentials/DeleteCredential'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export type CredentialListProps = { @@ -13,6 +14,7 @@ export type CredentialListProps = { const CredentialList = ({ projectName }: CredentialListProps) => { const { data } = useGetCredentialsQuery(projectName); + const [deleteCredential, { isLoading }] = useDeleteCredentialMutation(); const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -46,8 +48,20 @@ const CredentialList = ({ projectName }: CredentialListProp }, header: 'Status', }), + columnHelper.accessor((row: CredentialDto) => row.id, { + cell: (info) => ( + deleteCredential({ projectName, id }).unwrap()} + isLoading={isLoading} + /> + ), + header: 'Actions', + enableSorting: false, + }), ], - [columnHelper, projectName], + [columnHelper, deleteCredential, isLoading, projectName], ); return []} data={data || []} />; }; diff --git a/webapp/src/dogma/features/project/settings/credentials/DeleteCredential.tsx b/webapp/src/dogma/features/project/settings/credentials/DeleteCredential.tsx new file mode 100644 index 0000000000..79311dbded --- /dev/null +++ b/webapp/src/dogma/features/project/settings/credentials/DeleteCredential.tsx @@ -0,0 +1,46 @@ +import { Button, useDisclosure } from '@chakra-ui/react'; +import { newNotification } from 'dogma/features/notification/notificationSlice'; +import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; +import { useAppDispatch } from 'dogma/hooks'; +import { MdDelete } from 'react-icons/md'; +import { DeleteConfirmationModal } from 'dogma/features/project/settings/DeleteConfirmationModal'; + +export const DeleteCredential = ({ + projectName, + id, + deleteCredential, + isLoading, +}: { + projectName: string; + id: string; + deleteCredential: (projectName: string, id: string) => Promise; + isLoading: boolean; +}): JSX.Element => { + const { isOpen, onToggle, onClose } = useDisclosure(); + const dispatch = useAppDispatch(); + const handleDelete = async () => { + try { + await deleteCredential(projectName, id); + dispatch(newNotification('Credential deleted.', `Successfully deleted ${id}`, 'success')); + onClose(); + } catch (error) { + dispatch(newNotification(`Failed to delete ${id}`, ErrorMessageParser.parse(error), 'error')); + } + }; + return ( + <> + + + + ); +}; diff --git a/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx b/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx index af1506f053..484350a47f 100644 --- a/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx +++ b/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx @@ -1,19 +1,9 @@ -import { - Button, - HStack, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - useDisclosure, -} from '@chakra-ui/react'; +import { Button, useDisclosure } from '@chakra-ui/react'; import { newNotification } from 'dogma/features/notification/notificationSlice'; import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; import { useAppDispatch } from 'dogma/hooks'; import { MdDelete } from 'react-icons/md'; +import { DeleteConfirmationModal } from '../DeleteConfirmationModal'; export const DeleteMember = ({ projectName, @@ -41,29 +31,18 @@ export const DeleteMember = ({ }; return ( <> - - - - - Are you sure? - - - Delete {id} from {projectName}? - - - - - - - - - + ); }; diff --git a/webapp/src/dogma/features/project/settings/mirrors/DeleteMirror.tsx b/webapp/src/dogma/features/project/settings/mirrors/DeleteMirror.tsx new file mode 100644 index 0000000000..bc215d4170 --- /dev/null +++ b/webapp/src/dogma/features/project/settings/mirrors/DeleteMirror.tsx @@ -0,0 +1,46 @@ +import { Button, useDisclosure } from '@chakra-ui/react'; +import { DeleteConfirmationModal } from 'dogma/features/project/settings/DeleteConfirmationModal'; +import { newNotification } from 'dogma/features/notification/notificationSlice'; +import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; +import { useAppDispatch } from 'dogma/hooks'; +import { MdDelete } from 'react-icons/md'; + +export const DeleteMirror = ({ + projectName, + id, + deleteMirror, + isLoading, +}: { + projectName: string; + id: string; + deleteMirror: (projectName: string, id: string) => Promise; + isLoading: boolean; +}): JSX.Element => { + const { isOpen, onToggle, onClose } = useDisclosure(); + const dispatch = useAppDispatch(); + const handleDelete = async () => { + try { + await deleteMirror(projectName, id); + dispatch(newNotification('Mirror deleted.', `Successfully deleted ${id}`, 'success')); + onClose(); + } catch (error) { + dispatch(newNotification(`Failed to delete ${id}`, ErrorMessageParser.parse(error), 'error')); + } + }; + return ( + <> + + + + ); +}; diff --git a/webapp/src/dogma/features/project/settings/mirrors/MirrorList.tsx b/webapp/src/dogma/features/project/settings/mirrors/MirrorList.tsx index 7305902462..cf5e3bd2e4 100644 --- a/webapp/src/dogma/features/project/settings/mirrors/MirrorList.tsx +++ b/webapp/src/dogma/features/project/settings/mirrors/MirrorList.tsx @@ -1,13 +1,14 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; import React, { useMemo } from 'react'; import { DataTableClientPagination } from 'dogma/common/components/table/DataTableClientPagination'; -import { useGetMirrorsQuery } from 'dogma/features/api/apiSlice'; -import { Badge, Button, Code, Link } from '@chakra-ui/react'; +import { useGetMirrorsQuery, useDeleteMirrorMutation } from 'dogma/features/api/apiSlice'; +import { Badge, Button, Code, HStack, Link, Wrap, WrapItem } from '@chakra-ui/react'; import { GoRepo } from 'react-icons/go'; import { LabelledIcon } from 'dogma/common/components/LabelledIcon'; import { MirrorDto } from 'dogma/features/project/settings/mirrors/MirrorDto'; import { RunMirror } from '../../../mirror/RunMirrorButton'; import { FaPlay } from 'react-icons/fa'; +import { DeleteMirror } from 'dogma/features/project/settings/mirrors/DeleteMirror'; // eslint-disable-next-line @typescript-eslint/no-unused-vars export type MirrorListProps = { @@ -16,6 +17,7 @@ export type MirrorListProps = { const MirrorList = ({ projectName }: MirrorListProps) => { const { data } = useGetMirrorsQuery(projectName); + const [deleteMirror, { isLoading }] = useDeleteMirrorMutation(); const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -51,10 +53,6 @@ const MirrorList = ({ projectName }: MirrorListProps) cell: (info) => info.getValue(), header: 'Remote', }), - columnHelper.accessor((row: MirrorDto) => row.direction, { - cell: (info) => {info.getValue()}, - header: 'Direction', - }), columnHelper.accessor((row: MirrorDto) => row.schedule, { cell: (info) => { return ( @@ -65,28 +63,6 @@ const MirrorList = ({ projectName }: MirrorListProps) }, header: 'Schedule', }), - columnHelper.accessor((row: MirrorDto) => row.schedule, { - cell: (info) => { - return ( - - {({ isLoading, onToggle }) => ( - - )} - - ); - }, - header: 'Actions', - }), columnHelper.accessor((row: MirrorDto) => row.enabled, { cell: (info) => { if (info.getValue()) { @@ -97,8 +73,41 @@ const MirrorList = ({ projectName }: MirrorListProps) }, header: 'Status', }), + columnHelper.accessor((row: MirrorDto) => row.id, { + cell: (info) => ( + + + + + {({ isLoading, onToggle }) => ( + + )} + + + + deleteMirror({ projectName, id }).unwrap()} + isLoading={isLoading} + /> + + ), + header: 'Actions', + enableSorting: false, + }), ], - [columnHelper, projectName], + [columnHelper, deleteMirror, isLoading, projectName], ); return []} data={data || []} />; }; diff --git a/webapp/src/dogma/features/project/settings/tokens/AppTokenList.tsx b/webapp/src/dogma/features/project/settings/tokens/AppTokenList.tsx index 8421bce424..04ea0d1730 100644 --- a/webapp/src/dogma/features/project/settings/tokens/AppTokenList.tsx +++ b/webapp/src/dogma/features/project/settings/tokens/AppTokenList.tsx @@ -6,7 +6,7 @@ import { DataTableClientPagination } from 'dogma/common/components/table/DataTab import { useMemo } from 'react'; import { useDeleteTokenMemberMutation } from 'dogma/features/api/apiSlice'; import { AppTokenDetailDto } from 'dogma/features/project/settings/tokens/AppTokenDto'; -import { DeleteMember } from 'dogma/features/project/settings/members/DeleteMember'; +import { DeleteToken } from 'dogma/features/project/settings/tokens/DeleteToken'; export type AppTokenListProps = { data: Data[]; @@ -14,7 +14,7 @@ export type AppTokenListProps = { }; const AppTokenList = ({ data, projectName }: AppTokenListProps) => { - const [deleteMember, { isLoading }] = useDeleteTokenMemberMutation(); + const [deleteToken, { isLoading }] = useDeleteTokenMemberMutation(); const columnHelper = createColumnHelper(); const columns = useMemo( () => [ @@ -39,10 +39,10 @@ const AppTokenList = ({ data, projectName }: AppTokenListPr }), columnHelper.accessor((row: AppTokenDetailDto) => row.appId, { cell: (info) => ( - deleteMember({ projectName, id }).unwrap()} + deleteToken={(projectName, id) => deleteToken({ projectName, id }).unwrap()} isLoading={isLoading} /> ), @@ -50,7 +50,7 @@ const AppTokenList = ({ data, projectName }: AppTokenListPr enableSorting: false, }), ], - [columnHelper, deleteMember, isLoading, projectName], + [columnHelper, deleteToken, isLoading, projectName], ); return []} data={data} />; }; diff --git a/webapp/src/dogma/features/project/settings/tokens/DeleteToken.tsx b/webapp/src/dogma/features/project/settings/tokens/DeleteToken.tsx new file mode 100644 index 0000000000..adfbfd9ce7 --- /dev/null +++ b/webapp/src/dogma/features/project/settings/tokens/DeleteToken.tsx @@ -0,0 +1,48 @@ +import { Button, useDisclosure } from '@chakra-ui/react'; +import { newNotification } from 'dogma/features/notification/notificationSlice'; +import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; +import { useAppDispatch } from 'dogma/hooks'; +import { MdDelete } from 'react-icons/md'; +import { DeleteConfirmationModal } from '../DeleteConfirmationModal'; + +export const DeleteToken = ({ + projectName, + repoName, + id, + deleteToken, + isLoading, +}: { + projectName: string; + repoName?: string; + id: string; + deleteToken: (projectName: string, id: string, repoName?: string) => Promise; + isLoading: boolean; +}): JSX.Element => { + const { isOpen, onToggle, onClose } = useDisclosure(); + const dispatch = useAppDispatch(); + const handleDelete = async () => { + try { + await deleteToken(projectName, id, repoName); + dispatch(newNotification('Token deleted.', `Successfully deleted ${id}`, 'success')); + onClose(); + } catch (error) { + dispatch(newNotification(`Failed to delete ${id}`, ErrorMessageParser.parse(error), 'error')); + } + }; + return ( + <> + + + + ); +}; diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/AggregatingMultipleKubernetesTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/AggregatingMultipleKubernetesTest.java index 0784d1bcc5..fd32d90730 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/AggregatingMultipleKubernetesTest.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/AggregatingMultipleKubernetesTest.java @@ -122,13 +122,11 @@ void aggregateMultipleKubernetes() throws IOException { fooGroup.get(Revision.HEAD, Query.ofJson( K8S_ENDPOINT_AGGREGATORS_DIRECTORY + aggregatorId + ".json")).join(); assertAggregator(aggregatorEntry.contentAsText(), expectedAggregator); - System.err.println("aggregatorEntry.revision(): " + aggregatorEntry.revision()); await().until(() -> fooGroup.normalizeNow(Revision.HEAD).equals(aggregatorEntry.revision().forward(1))); final Entry endpointEntry = fooGroup.getOrNull(Revision.HEAD, Query.ofJson( K8S_ENDPOINTS_DIRECTORY + aggregatorId + ".json")).join(); - System.err.println("endpointEntry.revision(): " + endpointEntry.revision()); assertThatJson(endpointEntry.content()).isEqualTo( '{' + " \"clusterName\": \"groups/foo/k8s/clusters/foo-k8s-cluster/1\"," + @@ -193,7 +191,6 @@ void aggregateMultipleKubernetes() throws IOException { endpointEntry.revision().forward(2))); // 2 because of the aggregator update and endpoint update final Entry endpointEntry1 = fooGroup.getOrNull(Revision.HEAD, Query.ofJson( K8S_ENDPOINTS_DIRECTORY + aggregatorId + ".json")).join(); - System.err.println("endpointEntry.revision(): " + endpointEntry1.revision()); assertThatJson(endpointEntry1.content()).isEqualTo( '{' + " \"clusterName\": \"groups/foo/k8s/clusters/foo-k8s-cluster/1\"," +