From 9a06510490fb6ea9e35d18a053215942c83bcf2a Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Thu, 18 Jul 2024 12:38:20 +0100 Subject: [PATCH] E2C: Delete cloud migration token (#90548) --- .../features/migrate-to-cloud/api/index.ts | 8 ++- ...ionTokenModal.tsx => CreateTokenModal.tsx} | 2 +- .../DeleteTokenConfirmationModal.tsx | 42 +++++++++++++ .../MigrationTokenPane/MigrationTokenPane.tsx | 63 +++++++++++++++---- public/locales/en-US/grafana.json | 7 +++ public/locales/pseudo-LOCALE/grafana.json | 7 +++ 6 files changed, 113 insertions(+), 16 deletions(-) rename public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/{MigrationTokenModal.tsx => CreateTokenModal.tsx} (95%) create mode 100644 public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/DeleteTokenConfirmationModal.tsx diff --git a/public/app/features/migrate-to-cloud/api/index.ts b/public/app/features/migrate-to-cloud/api/index.ts index 921ff8c0f65..58e3bace435 100644 --- a/public/app/features/migrate-to-cloud/api/index.ts +++ b/public/app/features/migrate-to-cloud/api/index.ts @@ -8,13 +8,17 @@ export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ endpoints: { // Cloud-side - create token + getCloudMigrationToken(endpoint) { + suppressErrorsOnQuery(endpoint); + endpoint.providesTags = ['cloud-migration-token']; + }, createCloudMigrationToken(endpoint) { suppressErrorsOnQuery(endpoint); endpoint.invalidatesTags = ['cloud-migration-token']; }, - getCloudMigrationToken(endpoint) { + deleteCloudMigrationToken(endpoint) { suppressErrorsOnQuery(endpoint); - endpoint.providesTags = ['cloud-migration-token']; + endpoint.invalidatesTags = ['cloud-migration-token']; }, // List Cloud Configs diff --git a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/CreateTokenModal.tsx similarity index 95% rename from public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx rename to public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/CreateTokenModal.tsx index 76dc43fc341..d5a499a3af1 100644 --- a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenModal.tsx +++ b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/CreateTokenModal.tsx @@ -11,7 +11,7 @@ interface Props { migrationToken?: string; } -export const MigrationTokenModal = ({ isOpen, hideModal, migrationToken }: Props) => { +export const CreateTokenModal = ({ isOpen, hideModal, migrationToken }: Props) => { return ( void; + onDismiss: () => void; +} + +export function DeleteTokenConfirmationModal(props: DeleteTokenConfirmationModalProps) { + const { isOpen, hasError, onConfirm, onDismiss } = props; + + const body = ( + <> +

+ + If you've already used this token with a self-managed installation, that installation will no longer be + able to upload content. + +

+ + {hasError && ( + + )} + + ); + + return ( + + ); +} diff --git a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx index 0d39e955c57..16c7fa004f5 100644 --- a/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx +++ b/public/app/features/migrate-to-cloud/cloud/MigrationTokenPane/MigrationTokenPane.tsx @@ -4,10 +4,15 @@ import { isFetchError } from '@grafana/runtime'; import { Box, Button, Text } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; -import { useCreateCloudMigrationTokenMutation, useGetCloudMigrationTokenQuery } from '../../api'; +import { + useCreateCloudMigrationTokenMutation, + useDeleteCloudMigrationTokenMutation, + useGetCloudMigrationTokenQuery, +} from '../../api'; import { TokenErrorAlert } from '../TokenErrorAlert'; -import { MigrationTokenModal } from './MigrationTokenModal'; +import { CreateTokenModal } from './CreateTokenModal'; +import { DeleteTokenConfirmationModal } from './DeleteTokenConfirmationModal'; import { TokenStatus } from './TokenStatus'; // TODO: candidate to hoist and share @@ -29,22 +34,41 @@ function maybeAPIError(err: unknown) { } export const MigrationTokenPane = () => { - const [showModal, setShowModal] = useState(false); + const [showCreateModal, setShowCreateModal] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const getTokenQuery = useGetCloudMigrationTokenQuery(); const [createTokenMutation, createTokenResponse] = useCreateCloudMigrationTokenMutation(); + const [deleteTokenMutation, deleteTokenResponse] = useDeleteCloudMigrationTokenMutation(); const getTokenQueryError = maybeAPIError(getTokenQuery.error); - const hasToken = Boolean(createTokenResponse.data?.token) || Boolean(getTokenQuery.data?.id); + // GetCloudMigrationToken returns a 404 error if no token exists. + // When a token is deleted and the GetCloudMigrationToken query is refreshed, RTKQ will retain + // both the last successful data ("we have a token!") AND the new error. So we need to explicitly + // check that we don't have an error AND that we have a token. + const hasToken = Boolean(getTokenQuery.data?.id) && getTokenQueryError?.statusCode !== 404; const isLoading = getTokenQuery.isFetching || createTokenResponse.isLoading; const handleGenerateToken = useCallback(async () => { const resp = await createTokenMutation(); + if (!('error' in resp)) { - setShowModal(true); + setShowCreateModal(true); } }, [createTokenMutation]); + const handleDeleteToken = useCallback(async () => { + if (!getTokenQuery.data?.id) { + return; + } + + const resp = await deleteTokenMutation({ uid: getTokenQuery.data.id }); + if (!('error' in resp)) { + setShowDeleteModal(false); + } + }, [deleteTokenMutation, getTokenQuery.data]); + return ( <> @@ -59,18 +83,31 @@ export const MigrationTokenPane = () => { )} - + {hasToken ? ( + + ) : ( + + )} - setShowModal(false)} + setShowCreateModal(false)} migrationToken={createTokenResponse.data?.token} /> + + setShowDeleteModal(false)} + hasError={Boolean(deleteTokenResponse.error)} + /> ); }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b9cd212efdb..811a7fcc893 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -995,6 +995,12 @@ "button": "Migrate this instance to Cloud", "header": "Let us manage your Grafana stack" }, + "delete-migration-token-confirm": { + "body": "If you've already used this token with a self-managed installation, that installation will no longer be able to upload content.", + "confirm-button": "Delete token", + "error-title": "Error deleting token", + "title": "Delete migration token" + }, "disconnect-modal": { "body": "This will remove the migration token from this installation. If you wish to upload more resources in the future, you will need to enter a new migration token.", "cancel": "Cancel", @@ -1025,6 +1031,7 @@ "title": "Let us help you migrate to this stack" }, "migration-token": { + "delete-button": "Delete token", "delete-modal-body": "If you've already used this token with a self-managed installation, that installation will no longer be able to upload content.", "delete-modal-cancel": "Cancel", "delete-modal-confirm": "Delete", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 8f3c84115a4..bca9eb62df7 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -995,6 +995,12 @@ "button": "Mįģřäŧę ŧĥįş įʼnşŧäʼnčę ŧő Cľőūđ", "header": "Ŀęŧ ūş mäʼnäģę yőūř Ğřäƒäʼnä şŧäčĸ" }, + "delete-migration-token-confirm": { + "body": "Ĩƒ yőū'vę äľřęäđy ūşęđ ŧĥįş ŧőĸęʼn ŵįŧĥ ä şęľƒ-mäʼnäģęđ įʼnşŧäľľäŧįőʼn, ŧĥäŧ įʼnşŧäľľäŧįőʼn ŵįľľ ʼnő ľőʼnģęř þę äþľę ŧő ūpľőäđ čőʼnŧęʼnŧ.", + "confirm-button": "Đęľęŧę ŧőĸęʼn", + "error-title": "Ēřřőř đęľęŧįʼnģ ŧőĸęʼn", + "title": "Đęľęŧę mįģřäŧįőʼn ŧőĸęʼn" + }, "disconnect-modal": { "body": "Ŧĥįş ŵįľľ řęmővę ŧĥę mįģřäŧįőʼn ŧőĸęʼn ƒřőm ŧĥįş įʼnşŧäľľäŧįőʼn. Ĩƒ yőū ŵįşĥ ŧő ūpľőäđ mőřę řęşőūřčęş įʼn ŧĥę ƒūŧūřę, yőū ŵįľľ ʼnęęđ ŧő ęʼnŧęř ä ʼnęŵ mįģřäŧįőʼn ŧőĸęʼn.", "cancel": "Cäʼnčęľ", @@ -1025,6 +1031,7 @@ "title": "Ŀęŧ ūş ĥęľp yőū mįģřäŧę ŧő ŧĥįş şŧäčĸ" }, "migration-token": { + "delete-button": "Đęľęŧę ŧőĸęʼn", "delete-modal-body": "Ĩƒ yőū'vę äľřęäđy ūşęđ ŧĥįş ŧőĸęʼn ŵįŧĥ ä şęľƒ-mäʼnäģęđ įʼnşŧäľľäŧįőʼn, ŧĥäŧ įʼnşŧäľľäŧįőʼn ŵįľľ ʼnő ľőʼnģęř þę äþľę ŧő ūpľőäđ čőʼnŧęʼnŧ.", "delete-modal-cancel": "Cäʼnčęľ", "delete-modal-confirm": "Đęľęŧę",