E2C: Show snapshot error status (#91214)

* wip getError fn

* Create show errors from all API requests

* suppress error toasts from all e2c endpoints

* require error severity
This commit is contained in:
Josh Hunt
2024-07-31 16:04:40 +01:00
committed by GitHub
parent bffcb9de89
commit ff774e210c
6 changed files with 186 additions and 72 deletions
@@ -18,7 +18,7 @@ function createBackendSrvBaseQuery({ baseURL }: { baseURL: string }): BaseQueryF
getBackendSrv().fetch({ getBackendSrv().fetch({
...requestOptions, ...requestOptions,
url: baseURL + requestOptions.url, url: baseURL + requestOptions.url,
showErrorAlert: requestOptions.showErrorAlert, showErrorAlert: false,
data: requestOptions.body, data: requestOptions.body,
}) })
); );
@@ -8,50 +8,39 @@ export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({
endpoints: { endpoints: {
// Cloud-side - create token // Cloud-side - create token
getCloudMigrationToken(endpoint) { getCloudMigrationToken: {
suppressErrorsOnQuery(endpoint); providesTags: ['cloud-migration-token'],
endpoint.providesTags = ['cloud-migration-token'];
}, },
createCloudMigrationToken(endpoint) { createCloudMigrationToken: {
suppressErrorsOnQuery(endpoint); invalidatesTags: ['cloud-migration-token'],
endpoint.invalidatesTags = ['cloud-migration-token'];
}, },
deleteCloudMigrationToken(endpoint) { deleteCloudMigrationToken: {
suppressErrorsOnQuery(endpoint); invalidatesTags: ['cloud-migration-token'],
endpoint.invalidatesTags = ['cloud-migration-token'];
}, },
// List Cloud Configs // On-prem session management (entering token)
getSessionList: { getSessionList: {
providesTags: ['cloud-migration-session'] /* should this be a -list? */, providesTags: ['cloud-migration-session'] /* should this be a -list? */,
}, },
// Create Cloud Config
createSession(endpoint) {
suppressErrorsOnQuery(endpoint);
endpoint.invalidatesTags = ['cloud-migration-session'];
},
// Get one Cloud Config
getSession: { getSession: {
providesTags: ['cloud-migration-session'], providesTags: ['cloud-migration-session'],
}, },
createSession: {
// Delete one Cloud Config invalidatesTags: ['cloud-migration-session'],
},
deleteSession: { deleteSession: {
invalidatesTags: ['cloud-migration-session', 'cloud-migration-snapshot'], invalidatesTags: ['cloud-migration-session', 'cloud-migration-snapshot'],
}, },
// Snapshot management // Snapshot management
getSnapshot: {
providesTags: ['cloud-migration-snapshot'],
},
getShapshotList: { getShapshotList: {
providesTags: ['cloud-migration-snapshot'], providesTags: ['cloud-migration-snapshot'],
}, },
createSnapshot(endpoint) { getSnapshot: {
suppressErrorsOnQuery(endpoint); providesTags: ['cloud-migration-snapshot'],
endpoint.invalidatesTags = ['cloud-migration-snapshot']; },
createSnapshot: {
invalidatesTags: ['cloud-migration-snapshot'],
}, },
uploadSnapshot: { uploadSnapshot: {
invalidatesTags: ['cloud-migration-snapshot'], invalidatesTags: ['cloud-migration-snapshot'],
@@ -1,10 +1,11 @@
import { skipToken } from '@reduxjs/toolkit/query/react'; import { skipToken } from '@reduxjs/toolkit/query/react';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { Alert, Box, Stack, Text } from '@grafana/ui'; import { AlertVariant, Box, Stack, Text } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization'; import { Trans, t } from 'app/core/internationalization';
import { import {
GetSnapshotResponseDto,
SnapshotDto, SnapshotDto,
useCancelSnapshotMutation, useCancelSnapshotMutation,
useCreateSnapshotMutation, useCreateSnapshotMutation,
@@ -81,10 +82,12 @@ function useGetLatestSnapshot(sessionUid?: string, page = 1) {
skipPollingIfUnfocused: true, skipPollingIfUnfocused: true,
}); });
const isError = listResult.isError || snapshotResult.isError;
useEffect(() => { useEffect(() => {
const shouldPoll = SHOULD_POLL_STATUSES.includes(snapshotResult.data?.status); const shouldPoll = !isError && SHOULD_POLL_STATUSES.includes(snapshotResult.data?.status);
setShouldPoll(shouldPoll); setShouldPoll(shouldPoll);
}, [snapshotResult?.data?.status]); }, [snapshotResult?.data?.status, isError]);
return { return {
...snapshotResult, ...snapshotResult,
@@ -100,7 +103,7 @@ function useGetLatestSnapshot(sessionUid?: string, page = 1) {
// isSuccess and isUninitialised should always be from snapshotResult // isSuccess and isUninitialised should always be from snapshotResult
// as only the 'final' values from those are important // as only the 'final' values from those are important
isError: listResult.isError || snapshotResult.isError, isError,
isLoading: listResult.isLoading || snapshotResult.isLoading, isLoading: listResult.isLoading || snapshotResult.isLoading,
isFetching: listResult.isFetching || snapshotResult.isFetching, isFetching: listResult.isFetching || snapshotResult.isFetching,
}; };
@@ -131,11 +134,22 @@ export const Page = () => {
snapshot.isLoading || snapshot.isLoading ||
disconnectResult.isLoading; disconnectResult.isLoading;
const showBuildSnapshot = !snapshot.isLoading && !snapshot.data; const showBuildSnapshot = !snapshot.isError && !snapshot.isLoading && !snapshot.data;
const showBuildingSnapshot = SNAPSHOT_BUILDING_STATUSES.includes(status); const showBuildingSnapshot = SNAPSHOT_BUILDING_STATUSES.includes(status);
const showUploadSnapshot = status === 'PENDING_UPLOAD' || SNAPSHOT_UPLOADING_STATUSES.includes(status); const showUploadSnapshot =
!snapshot.isError && (status === 'PENDING_UPLOAD' || SNAPSHOT_UPLOADING_STATUSES.includes(status));
const showRebuildSnapshot = SNAPSHOT_REBUILD_STATUSES.includes(status); const showRebuildSnapshot = SNAPSHOT_REBUILD_STATUSES.includes(status);
const error = getError({
snapshot: snapshot.data,
getSnapshotError: snapshot.error,
getSessionError: session.error,
createSnapshotError: createSnapshotResult.error,
uploadSnapshotError: uploadSnapshotResult.error,
cancelSnapshotError: cancelSnapshotResult.error,
disconnectSnapshotError: disconnectResult.error,
});
const handleDisconnect = useCallback(async () => { const handleDisconnect = useCallback(async () => {
if (sessionUid) { if (sessionUid) {
performDisconnect({ uid: sessionUid }); performDisconnect({ uid: sessionUid });
@@ -173,34 +187,7 @@ export const Page = () => {
return ( return (
<> <>
<Stack direction="column" gap={4}> <Stack direction="column" gap={2}>
{/* TODO: show errors from all mutation's in a... modal? */}
{createSnapshotResult.isError && (
<AlertWithTraceID
error={createSnapshotResult.error}
severity="error"
title={t('migrate-to-cloud.summary.run-migration-error-title', 'Error creating snapshot')}
>
<Text element="p">
<Trans i18nKey="migrate-to-cloud.summary.run-migration-error-description">
See the Grafana server logs for more details
</Trans>
</Text>
</AlertWithTraceID>
)}
{disconnectResult.isError && (
<Alert
severity="error"
title={t('migrate-to-cloud.summary.disconnect-error-title', 'There was an error disconnecting')}
>
<Trans i18nKey="migrate-to-cloud.summary.disconnect-error-description">
See the Grafana server logs for more details
</Trans>
</Alert>
)}
{session.data && ( {session.data && (
<MigrationSummary <MigrationSummary
session={session.data} session={session.data}
@@ -218,6 +205,12 @@ export const Page = () => {
/> />
)} )}
{error && (
<AlertWithTraceID severity={error.severity} title={error.title} error={error.error}>
<Text element="p">{error.body}</Text>
</AlertWithTraceID>
)}
{(showBuildSnapshot || showBuildingSnapshot) && ( {(showBuildSnapshot || showBuildingSnapshot) && (
<Box display="flex" justifyContent="center" paddingY={10}> <Box display="flex" justifyContent="center" paddingY={10}>
{showBuildSnapshot && ( {showBuildSnapshot && (
@@ -258,3 +251,113 @@ export const Page = () => {
</> </>
); );
}; };
interface GetErrorProps {
snapshot: GetSnapshotResponseDto | undefined;
getSessionError: unknown; // From getLatestSessionQuery
getSnapshotError: unknown; // From getLatestSnapshotQuery
createSnapshotError: unknown; // From createSnapshotMutation
uploadSnapshotError: unknown; // From uploadSnapshotMutation
cancelSnapshotError: unknown; // From cancelSnapshotMutation
disconnectSnapshotError: unknown; // From disconnectMutation
}
interface ErrorDescription {
title: string;
body: string;
severity: AlertVariant;
error?: unknown;
}
function getError(props: GetErrorProps): ErrorDescription | undefined {
const {
snapshot,
getSnapshotError,
getSessionError,
createSnapshotError,
uploadSnapshotError,
cancelSnapshotError,
disconnectSnapshotError,
} = props;
const seeLogs = t('migrate-to-cloud.onprem.error-see-server-logs', 'See the Grafana server logs for more details');
if (getSessionError) {
return {
severity: 'error',
title: t('migrate-to-cloud.onprem.get-session-error-title', 'Error loading migration configuration'),
body: seeLogs,
error: getSessionError,
};
}
if (getSnapshotError) {
return {
severity: 'error',
title: t('migrate-to-cloud.onprem.get-snapshot-error-title', 'Error loading snapshot'),
body: seeLogs,
error: getSnapshotError,
};
}
if (disconnectSnapshotError) {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.disconnect-error-title', 'Error disconnecting'),
body: seeLogs,
error: disconnectSnapshotError,
};
}
if (createSnapshotError) {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.create-snapshot-error-title', 'Error creating snapshot'),
body: seeLogs,
error: createSnapshotError,
};
}
if (uploadSnapshotError) {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.upload-snapshot-error-title', 'Error uploading snapshot'),
body: seeLogs,
error: uploadSnapshotError,
};
}
if (cancelSnapshotError) {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.cancel-snapshot-error-title', 'Error cancelling creating snapshot'),
body: seeLogs,
error: cancelSnapshotError,
};
}
if (snapshot?.status === 'ERROR') {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.snapshot-error-status-title', 'Error migrating resources'),
body: t(
'migrate-to-cloud.onprem.snapshot-error-status-body',
'There was an error creating the snapshot or starting the migration process. See the Grafana server logs for more details'
),
};
}
const errorCount = snapshot?.stats?.statuses?.['ERROR'] ?? 0;
if (snapshot?.status === 'FINISHED' && errorCount > 0) {
return {
severity: 'warning',
title: t('migrate-to-cloud.onprem.some-resources-errored-title', 'Resource migration complete'),
body: t(
'migrate-to-cloud.onprem.some-resources-errored-body',
'The migration has completed, but some items could not be migrated to the cloud stack. Check the failed resources for more details'
),
};
}
return undefined;
}
@@ -15,9 +15,13 @@ export function AlertWithTraceID(props: AlertWithTraceIDProps) {
<Stack direction="column" gap={1}> <Stack direction="column" gap={1}>
{children} {children}
{/* Deliberately don't want to translate 'Trace ID' */} {traceID && (
{/* eslint-disable-next-line @grafana/no-untranslated-strings */} /* Deliberately don't want to translate 'Trace ID' */
{traceID && <Text element="p">Trace ID: {traceID}</Text>} /* eslint-disable-next-line @grafana/no-untranslated-strings */
<Text element="p" color="secondary" variant="bodySmall">
Trace ID: {traceID}
</Text>
)}
</Stack> </Stack>
</Alert> </Alert>
); );
+13 -4
View File
@@ -1111,6 +1111,19 @@
"modal-title": "Migration token created", "modal-title": "Migration token created",
"status": "Current status: <2></2>" "status": "Current status: <2></2>"
}, },
"onprem": {
"cancel-snapshot-error-title": "Error cancelling creating snapshot",
"create-snapshot-error-title": "Error creating snapshot",
"disconnect-error-title": "Error disconnecting",
"error-see-server-logs": "See the Grafana server logs for more details",
"get-session-error-title": "Error loading migration configuration",
"get-snapshot-error-title": "Error loading snapshot",
"snapshot-error-status-body": "There was an error creating the snapshot or starting the migration process. See the Grafana server logs for more details",
"snapshot-error-status-title": "Error migrating resources",
"some-resources-errored-body": "The migration has completed, but some items could not be migrated to the cloud stack. Check the failed resources for more details",
"some-resources-errored-title": "Resource migration complete",
"upload-snapshot-error-title": "Error uploading snapshot"
},
"pdc": { "pdc": {
"body": "Exposing your data sources to the internet can raise security concerns. Private data source connect (PDC) allows Grafana Cloud to access your existing data sources over a secure network tunnel.", "body": "Exposing your data sources to the internet can raise security concerns. Private data source connect (PDC) allows Grafana Cloud to access your existing data sources over a secure network tunnel.",
"link-title": "Learn about PDC", "link-title": "Learn about PDC",
@@ -1154,13 +1167,9 @@
"summary": { "summary": {
"cancel-snapshot": "Cancel snapshot", "cancel-snapshot": "Cancel snapshot",
"disconnect": "Disconnect", "disconnect": "Disconnect",
"disconnect-error-description": "See the Grafana server logs for more details",
"disconnect-error-title": "There was an error disconnecting",
"errored-resource-count": "Errors", "errored-resource-count": "Errors",
"page-loading": "Loading...", "page-loading": "Loading...",
"rebuild-snapshot": "Rebuild snapshot", "rebuild-snapshot": "Rebuild snapshot",
"run-migration-error-description": "See the Grafana server logs for more details",
"run-migration-error-title": "Error creating snapshot",
"snapshot-date": "Snapshot timestamp", "snapshot-date": "Snapshot timestamp",
"snapshot-not-created": "Not yet created", "snapshot-not-created": "Not yet created",
"start-migration": "Build snapshot", "start-migration": "Build snapshot",
+13 -4
View File
@@ -1111,6 +1111,19 @@
"modal-title": "Mįģřäŧįőʼn ŧőĸęʼn čřęäŧęđ", "modal-title": "Mįģřäŧįőʼn ŧőĸęʼn čřęäŧęđ",
"status": "Cūřřęʼnŧ şŧäŧūş: <2></2>" "status": "Cūřřęʼnŧ şŧäŧūş: <2></2>"
}, },
"onprem": {
"cancel-snapshot-error-title": "Ēřřőř čäʼnčęľľįʼnģ čřęäŧįʼnģ şʼnäpşĥőŧ",
"create-snapshot-error-title": "Ēřřőř čřęäŧįʼnģ şʼnäpşĥőŧ",
"disconnect-error-title": "Ēřřőř đįşčőʼnʼnęčŧįʼnģ",
"error-see-server-logs": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
"get-session-error-title": "Ēřřőř ľőäđįʼnģ mįģřäŧįőʼn čőʼnƒįģūřäŧįőʼn",
"get-snapshot-error-title": "Ēřřőř ľőäđįʼnģ şʼnäpşĥőŧ",
"snapshot-error-status-body": "Ŧĥęřę ŵäş äʼn ęřřőř čřęäŧįʼnģ ŧĥę şʼnäpşĥőŧ őř şŧäřŧįʼnģ ŧĥę mįģřäŧįőʼn přőčęşş. Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
"snapshot-error-status-title": "Ēřřőř mįģřäŧįʼnģ řęşőūřčęş",
"some-resources-errored-body": "Ŧĥę mįģřäŧįőʼn ĥäş čőmpľęŧęđ, þūŧ şőmę įŧęmş čőūľđ ʼnőŧ þę mįģřäŧęđ ŧő ŧĥę čľőūđ şŧäčĸ. Cĥęčĸ ŧĥę ƒäįľęđ řęşőūřčęş ƒőř mőřę đęŧäįľş",
"some-resources-errored-title": "Ŗęşőūřčę mįģřäŧįőʼn čőmpľęŧę",
"upload-snapshot-error-title": "Ēřřőř ūpľőäđįʼnģ şʼnäpşĥőŧ"
},
"pdc": { "pdc": {
"body": "Ēχpőşįʼnģ yőūř đäŧä şőūřčęş ŧő ŧĥę įʼnŧęřʼnęŧ čäʼn řäįşę şęčūřįŧy čőʼnčęřʼnş. Přįväŧę đäŧä şőūřčę čőʼnʼnęčŧ (PĐC) äľľőŵş Ğřäƒäʼnä Cľőūđ ŧő äččęşş yőūř ęχįşŧįʼnģ đäŧä şőūřčęş ővęř ä şęčūřę ʼnęŧŵőřĸ ŧūʼnʼnęľ.", "body": "Ēχpőşįʼnģ yőūř đäŧä şőūřčęş ŧő ŧĥę įʼnŧęřʼnęŧ čäʼn řäįşę şęčūřįŧy čőʼnčęřʼnş. Přįväŧę đäŧä şőūřčę čőʼnʼnęčŧ (PĐC) äľľőŵş Ğřäƒäʼnä Cľőūđ ŧő äččęşş yőūř ęχįşŧįʼnģ đäŧä şőūřčęş ővęř ä şęčūřę ʼnęŧŵőřĸ ŧūʼnʼnęľ.",
"link-title": "Ŀęäřʼn äþőūŧ PĐC", "link-title": "Ŀęäřʼn äþőūŧ PĐC",
@@ -1154,13 +1167,9 @@
"summary": { "summary": {
"cancel-snapshot": "Cäʼnčęľ şʼnäpşĥőŧ", "cancel-snapshot": "Cäʼnčęľ şʼnäpşĥőŧ",
"disconnect": "Đįşčőʼnʼnęčŧ", "disconnect": "Đįşčőʼnʼnęčŧ",
"disconnect-error-description": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
"disconnect-error-title": "Ŧĥęřę ŵäş äʼn ęřřőř đįşčőʼnʼnęčŧįʼnģ",
"errored-resource-count": "Ēřřőřş", "errored-resource-count": "Ēřřőřş",
"page-loading": "Ŀőäđįʼnģ...", "page-loading": "Ŀőäđįʼnģ...",
"rebuild-snapshot": "Ŗęþūįľđ şʼnäpşĥőŧ", "rebuild-snapshot": "Ŗęþūįľđ şʼnäpşĥőŧ",
"run-migration-error-description": "Ŝęę ŧĥę Ğřäƒäʼnä şęřvęř ľőģş ƒőř mőřę đęŧäįľş",
"run-migration-error-title": "Ēřřőř čřęäŧįʼnģ şʼnäpşĥőŧ",
"snapshot-date": "Ŝʼnäpşĥőŧ ŧįmęşŧämp", "snapshot-date": "Ŝʼnäpşĥőŧ ŧįmęşŧämp",
"snapshot-not-created": "Ńőŧ yęŧ čřęäŧęđ", "snapshot-not-created": "Ńőŧ yęŧ čřęäŧęđ",
"start-migration": "ßūįľđ şʼnäpşĥőŧ", "start-migration": "ßūįľđ şʼnäpşĥőŧ",