diff --git a/workspaces/extensions/.changeset/popular-pumpkins-punch.md b/workspaces/extensions/.changeset/popular-pumpkins-punch.md
new file mode 100644
index 0000000000..2dbbaaf340
--- /dev/null
+++ b/workspaces/extensions/.changeset/popular-pumpkins-punch.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-extensions': patch
+---
+
+show error in the UI when toggle action fails
diff --git a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx
index 658d024fc7..d5ef314506 100644
--- a/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx
+++ b/workspaces/extensions/plugins/extensions/src/components/ExtensionsPackageEditContent.tsx
@@ -55,6 +55,7 @@ import { TabPanel } from './TabPanel';
import { useInstallationContext } from './InstallationContext';
import { useTranslation } from '../hooks/useTranslation';
import { ExtensionsStatus, getPluginActionTooltipMessage } from '../utils';
+import { InstallationWarning } from './InstallationWarning';
interface TabItem {
label: string;
@@ -197,6 +198,13 @@ export const ExtensionsPackageEditContent = ({
const showRightCard = hasPackageExamples;
+ const showEditWarning =
+ (pkgConfig.data as any)?.error?.message &&
+ (pkgConfig.data as any)?.error?.reason !==
+ ExtensionsStatus.INSTALLATION_DISABLED &&
+ (pkgConfig.data as any)?.error?.reason !==
+ ExtensionsStatus.INSTALLATION_DISABLED_IN_PRODUCTION;
+
const handleSave = async () => {
try {
setSaveError(null);
@@ -279,6 +287,12 @@ export const ExtensionsPackageEditContent = ({
return (
<>
+ {showEditWarning && }
+ {saveError && (
+
+ {saveError}
+
+ )}
{!pkg.spec?.dynamicArtifact && (
{t('alert.missingDynamicArtifactTitle')}
diff --git a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/InstalledPackagesTable.tsx b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/InstalledPackagesTable.tsx
index 9c24e31518..042df25c80 100644
--- a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/InstalledPackagesTable.tsx
+++ b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/InstalledPackagesTable.tsx
@@ -27,6 +27,8 @@ import { Query, QueryResult } from '@material-table/core';
import { useQuery } from '@tanstack/react-query';
import Box from '@mui/material/Box';
+import Snackbar from '@mui/material/Snackbar';
+import Alert from '@mui/material/Alert';
import {
ExtensionsPackage,
ExtensionsPackageInstallStatus,
@@ -58,6 +60,8 @@ import {
export const InstalledPackagesTable = () => {
const { t } = useTranslation();
+ const [rowActionError, setRowActionError] = useState(null);
+ const [snackbarOpen, setSnackbarOpen] = useState(false);
const extensionsConfig = useExtensionsConfiguration();
const { installedPackages } = useInstallationContext();
const nodeEnvironment = useNodeEnvironment();
@@ -187,11 +191,19 @@ export const InstalledPackagesTable = () => {
{
+ setRowActionError(err);
+ setSnackbarOpen(true);
+ }}
/>
{
+ setRowActionError(err);
+ setSnackbarOpen(true);
+ }}
/>
);
@@ -359,6 +371,31 @@ export const InstalledPackagesTable = () => {
onClose={setOpenInstalledPackagesDialog}
showPackages
/>
+ {
+ setSnackbarOpen(false);
+ setRowActionError(null);
+ }}
+ anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
+ sx={{
+ top: '80px !important',
+ }}
+ >
+ {
+ setSnackbarOpen(false);
+ setRowActionError(null);
+ }}
+ severity="error"
+ sx={{ width: '100%' }}
+ >
+ {Array.isArray(rowActionError)
+ ? rowActionError.map((err, idx) => {err}
)
+ : rowActionError}
+
+
>
);
};
diff --git a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx
index 580273a902..09724d01a1 100644
--- a/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx
+++ b/workspaces/extensions/plugins/extensions/src/components/InstalledPackages/RowActions.tsx
@@ -32,6 +32,7 @@ import { ExtensionsPackageInstallStatus } from '@red-hat-developer-hub/backstage
import { useTranslation } from '../../hooks/useTranslation';
import { packageInstallRouteRef, packageRouteRef } from '../../routes';
import { usePackageConfig } from '../../hooks/usePackageConfig';
+import { usePackage } from '../../hooks/usePackage';
import { downloadPackageYAML } from '../../utils/downloadPackageYaml';
import { usePluginConfigurationPermissions } from '../../hooks/usePluginConfigurationPermissions';
import { useEnablePlugin } from '../../hooks/useEnablePlugin';
@@ -53,12 +54,15 @@ export type InstalledPackageRow = {
export const DownloadPackageYaml = ({
pkg,
isProductionEnv,
+ onError,
}: {
pkg: InstalledPackageRow;
isProductionEnv: boolean;
+ onError?: (error: string) => void;
}) => {
const { t } = useTranslation();
const pkgConfig = usePackageConfig(pkg.namespace!, pkg.name!);
+ const packageEntity = usePackage(pkg.namespace!, pkg.name!);
const packageConfigPermission = usePluginConfigurationPermissions(
pkg.namespace!,
pkg.parentPlugin ?? '',
@@ -108,10 +112,31 @@ export const DownloadPackageYaml = ({
size="small"
sx={{ color: theme => theme.palette.text.primary }}
onClick={async () => {
- await downloadPackageYAML(
- pkgConfig.data?.configYaml ?? '',
- pkg.name!,
- );
+ try {
+ const configYaml = pkgConfig.data?.configYaml ?? '';
+ if (!configYaml) {
+ const dynamicArtifact =
+ packageEntity.data?.spec?.dynamicArtifact ??
+ `./dynamic-plugins/dist/${pkg.packageName}`;
+ const minimalYaml = `plugins:
+ - package: ${JSON.stringify(dynamicArtifact)}
+ disabled: false
+`;
+ // eslint-disable-next-line no-console
+ console.info(
+ `No configuration found for package ${pkg.name}, downloaded a minimal YAML`,
+ );
+ await downloadPackageYAML(minimalYaml, pkg.name!);
+ return;
+ }
+ await downloadPackageYAML(configYaml, pkg.name!);
+ } catch (err: any) {
+ const errorMessage =
+ err?.message || err?.toString() || 'Unknown error occurred';
+ onError?.(
+ `Failed to download package ${pkg.name}: ${errorMessage}`,
+ );
+ }
}}
>
@@ -235,10 +260,12 @@ export const TogglePackage = ({
pkg,
isProductionEnv,
isInstallationEnabled,
+ onError,
}: {
pkg: InstalledPackageRow;
isProductionEnv: boolean;
isInstallationEnabled: boolean;
+ onError?: (error: string) => void;
}) => {
const { t } = useTranslation();
const { installedPackages, setInstalledPackages } = useInstallationContext();
@@ -323,17 +350,20 @@ export const TogglePackage = ({
};
setInstalledPackages(updated);
} else {
- // eslint-disable-next-line no-console
- console.warn(
- `[Package Toggle] Package toggle responded with non-OK status:`,
- (res as any)?.error?.message ?? res,
+ const errorMessage =
+ (res as any)?.error?.message ?? res?.toString() ?? 'Unknown error';
+ onError?.(
+ `Failed to ${isPackageEnabled ? 'disable' : 'enable'} package ${pkg.name}: ${errorMessage}`,
);
}
} catch (err: any) {
- // eslint-disable-next-line no-console
- console.error(
- `[Package Toggle] Failed to toggle package}:`,
- err?.error?.message ?? err,
+ const errorMessage =
+ err?.error?.message ??
+ err?.message ??
+ err?.toString() ??
+ 'Unknown error';
+ onError?.(
+ `Failed to ${isPackageEnabled ? 'disable' : 'enable'} package ${pkg.name}: ${errorMessage}`,
);
}
};
diff --git a/workspaces/extensions/plugins/extensions/src/pages/ExtensionsPackageInstallPage.tsx b/workspaces/extensions/plugins/extensions/src/pages/ExtensionsPackageInstallPage.tsx
index 54a977a376..ea55389e5e 100644
--- a/workspaces/extensions/plugins/extensions/src/pages/ExtensionsPackageInstallPage.tsx
+++ b/workspaces/extensions/plugins/extensions/src/pages/ExtensionsPackageInstallPage.tsx
@@ -35,7 +35,7 @@ const PackageEditHeader = () => {
const pkg = usePackage(params.namespace, params.name);
- const displayName = pkg.data?.metadata.title ?? params.name;
+ const displayName = pkg.data?.metadata?.title ?? params.name;
const title =
location?.state?.viewOnly || !pkg.data?.spec?.dynamicArtifact
? displayName