E2C: Delete cloud migration token (#90548)

This commit is contained in:
Josh Hunt 2024-07-18 12:38:20 +01:00 committed by GitHub
parent f8645f73ea
commit 9a06510490
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 113 additions and 16 deletions

View File

@ -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

View File

@ -11,7 +11,7 @@ interface Props {
migrationToken?: string;
}
export const MigrationTokenModal = ({ isOpen, hideModal, migrationToken }: Props) => {
export const CreateTokenModal = ({ isOpen, hideModal, migrationToken }: Props) => {
return (
<Modal
isOpen={isOpen}

View File

@ -0,0 +1,42 @@
import { Alert, ConfirmModal } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
interface DeleteTokenConfirmationModalProps {
isOpen: boolean;
hasError: boolean;
onConfirm: () => void;
onDismiss: () => void;
}
export function DeleteTokenConfirmationModal(props: DeleteTokenConfirmationModalProps) {
const { isOpen, hasError, onConfirm, onDismiss } = props;
const body = (
<>
<p>
<Trans i18nKey="migrate-to-cloud.delete-migration-token-confirm.body">
If you&apos;ve already used this token with a self-managed installation, that installation will no longer be
able to upload content.
</Trans>
</p>
{hasError && (
<Alert
severity="error"
title={t('migrate-to-cloud.delete-migration-token-confirm.error-title', 'Error deleting token')}
/>
)}
</>
);
return (
<ConfirmModal
isOpen={isOpen}
title={t('migrate-to-cloud.delete-migration-token-confirm.title', 'Delete migration token')}
body={body}
confirmText={t('migrate-to-cloud.delete-migration-token-confirm.confirm-button', 'Delete token')}
onConfirm={onConfirm}
onDismiss={onDismiss}
/>
);
}

View File

@ -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 (
<>
<Box display="flex" alignItems="flex-start" direction="column" gap={2}>
@ -59,18 +83,31 @@ export const MigrationTokenPane = () => {
</Text>
)}
<Button disabled={isLoading || hasToken} onClick={handleGenerateToken}>
{createTokenResponse.isLoading
? t('migrate-to-cloud.migration-token.generate-button-loading', 'Generating a migration token...')
: t('migrate-to-cloud.migration-token.generate-button', 'Generate a migration token')}
</Button>
{hasToken ? (
<Button onClick={() => setShowDeleteModal(true)} variant="destructive">
{t('migrate-to-cloud.migration-token.delete-button', 'Delete token')}
</Button>
) : (
<Button disabled={isLoading} onClick={handleGenerateToken}>
{createTokenResponse.isLoading
? t('migrate-to-cloud.migration-token.generate-button-loading', 'Generating a migration token...')
: t('migrate-to-cloud.migration-token.generate-button', 'Generate a migration token')}
</Button>
)}
</Box>
<MigrationTokenModal
isOpen={showModal}
hideModal={() => setShowModal(false)}
<CreateTokenModal
isOpen={showCreateModal}
hideModal={() => setShowCreateModal(false)}
migrationToken={createTokenResponse.data?.token}
/>
<DeleteTokenConfirmationModal
isOpen={showDeleteModal}
onConfirm={handleDeleteToken}
onDismiss={() => setShowDeleteModal(false)}
hasError={Boolean(deleteTokenResponse.error)}
/>
</>
);
};

View File

@ -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",

View File

@ -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": "Đęľęŧę",