diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx index 38511a2dfea..16ec1d3ca70 100644 --- a/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx +++ b/public/app/features/plugins/admin/components/VersionInstallButton.test.tsx @@ -177,6 +177,46 @@ describe('VersionInstallButton', () => { ); expect(screen.getByText('Downgrade')).not.toBeVisible(); }); + + it('should show the installation button if invalid semver version is provided', () => { + const version: Version = { + version: '1.0.a', + createdAt: '', + isCompatible: false, + grafanaDependency: null, + }; + const installedVersion = '1.0.1'; + renderWithStore( + {}} + /> + ); + expect(screen.getByText('Install')).toBeInTheDocument(); + }); + + it('should show the installation button if invalid semver installed version is provided', () => { + const version: Version = { + version: '1.0.0', + createdAt: '', + isCompatible: false, + grafanaDependency: null, + }; + const installedVersion = '1.0.a'; + renderWithStore( + {}} + /> + ); + expect(screen.getByText('Install')).toBeInTheDocument(); + }); }); function renderWithStore(component: JSX.Element) { diff --git a/public/app/features/plugins/admin/components/VersionInstallButton.tsx b/public/app/features/plugins/admin/components/VersionInstallButton.tsx index 1da8bfe01f7..f0bec011875 100644 --- a/public/app/features/plugins/admin/components/VersionInstallButton.tsx +++ b/public/app/features/plugins/admin/components/VersionInstallButton.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { useEffect, useState } from 'react'; -import { gt } from 'semver'; +import { gt, valid } from 'semver'; import { GrafanaTheme2 } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; @@ -14,6 +14,12 @@ import { Version } from '../types'; const PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME = 'plugins_upgrade_clicked'; const PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME = 'plugins_downgrade_clicked'; +enum InstallState { + INSTALL = 'Install', + UPGRADE = 'Upgrade', + DOWNGRADE = 'Downgrade', +} + interface Props { pluginId: string; version: Version; @@ -38,7 +44,7 @@ export const VersionInstallButton = ({ const [isModalOpen, setIsModalOpen] = useState(false); const styles = useStyles2(getStyles); - const isDowngrade = installedVersion && gt(installedVersion, version.version); + const installState = getInstallState(installedVersion, version.version); useEffect(() => { if (installedVersion === version.version) { @@ -61,7 +67,7 @@ export const VersionInstallButton = ({ schema_version: '1.0.0', }; - if (!installedVersion || gt(version.version, installedVersion)) { + if (installState === InstallState.UPGRADE) { reportInteraction(PLUGINS_VERSION_PAGE_UPGRADE_INTERACTION_EVENT_NAME, trackProps); } else { reportInteraction(PLUGINS_VERSION_PAGE_CHANGE_INTERACTION_EVENT_NAME, { @@ -76,7 +82,7 @@ export const VersionInstallButton = ({ }; const onInstallClick = () => { - if (isDowngrade) { + if (installState === InstallState.DOWNGRADE) { setIsModalOpen(true); } else { performInstallation(); @@ -91,24 +97,9 @@ export const VersionInstallButton = ({ setIsModalOpen(false); }; - let label = 'Downgrade'; - let hidden = false; const isPreinstalled = isPreinstalledPlugin(pluginId); - if (!installedVersion) { - label = 'Install'; - } else if (gt(version.version, installedVersion)) { - label = 'Upgrade'; - if (isPreinstalled.withVersion) { - // Hide button if the plugin is preinstalled with a specific version - hidden = true; - } - } else { - if (isPreinstalled.found && Boolean(config.featureToggles.preinstallAutoUpdate)) { - // Hide the downgrade button if the plugin is preinstalled since it will be auto-updated - hidden = true; - } - } + const hidden = getButtonHiddenState(installState, isPreinstalled); return ( <> @@ -124,7 +115,7 @@ export const VersionInstallButton = ({ tooltip={tooltip} tooltipPlacement="bottom-start" > - {label} {isInstalling ? : getIcon(label)} + {installState} {isInstalling ? : getIcon(installState)} ; } - if (label === 'Upgrade') { + if (installState === InstallState.UPGRADE) { return ; } return ''; } +function getInstallState(installedVersion?: string, version?: string): InstallState { + if (!installedVersion || !version || !valid(installedVersion) || !valid(version)) { + return InstallState.INSTALL; + } + return gt(installedVersion, version) ? InstallState.DOWNGRADE : InstallState.UPGRADE; +} + +function getButtonHiddenState(installState: InstallState, isPreinstalled: { found: boolean; withVersion: boolean }) { + // Default state for initial install + if (installState === InstallState.INSTALL) { + return false; + } + + // Handle downgrade case + if (installState === InstallState.DOWNGRADE) { + return isPreinstalled.found && Boolean(config.featureToggles.preinstallAutoUpdate); + } + + // Handle upgrade case + return isPreinstalled.withVersion; +} + const getStyles = (theme: GrafanaTheme2) => ({ spinner: css({ marginLeft: theme.spacing(1),