Adapt plugin page to preinstalled plugins (#91874)

This commit is contained in:
Andres Martinez Gotor 2024-08-14 17:04:59 +02:00 committed by GitHub
parent 340af8cf6b
commit 44290ddf32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 94 additions and 14 deletions

View File

@ -42,6 +42,11 @@ export type AppPluginConfig = {
angular: AngularMeta;
};
export type PreinstalledPlugin = {
id: string;
version: string;
};
export class GrafanaBootConfig implements GrafanaConfig {
publicDashboardAccessToken?: string;
publicDashboardsEnabled = true;
@ -124,6 +129,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
pluginAdminExternalManageEnabled = false;
pluginCatalogHiddenPlugins: string[] = [];
pluginCatalogManagedPlugins: string[] = [];
pluginCatalogPreinstalledPlugins: PreinstalledPlugin[] = [];
pluginsCDNBaseURL = '';
expressionsEnabled = false;
customTheme?: undefined;

View File

@ -228,6 +228,7 @@ type FrontendSettingsDTO struct {
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"`
ExpressionsEnabled bool `json:"expressionsEnabled"`
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`

View File

@ -274,6 +274,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins,
PluginCatalogManagedPlugins: hs.managedPluginsService.ManagedPlugins(c.Req.Context()),
PluginCatalogPreinstalledPlugins: hs.Cfg.InstallPlugins,
ExpressionsEnabled: hs.Cfg.ExpressionsEnabled,
AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders,
AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled,

View File

@ -524,8 +524,8 @@ type Cfg struct {
}
type InstallPlugin struct {
ID string
Version string
ID string `json:"id"`
Version string `json:"version"`
}
// AddChangePasswordLink returns if login form is disabled or not since

View File

@ -21,6 +21,7 @@ export default {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
name: 'Zabbix',
orgName: 'Alexander Zobnin',
popularity: 0.2093,

View File

@ -33,6 +33,7 @@ const plugin: CatalogPlugin = {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
};
describe('GetStartedWithDataSource', () => {

View File

@ -33,6 +33,7 @@ const plugin: CatalogPlugin = {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
};
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
@ -253,5 +254,17 @@ describe('InstallControlsButton', () => {
);
expect(screen.queryByText('Update')).not.toBeInTheDocument();
});
it('should be hidden when plugin is preinstalled with a specific version', () => {
render(
<TestProvider>
<InstallControlsButton
plugin={{ ...plugin, isPreinstalled: { found: true, withVersion: true } }}
pluginStatus={PluginStatus.UPDATE}
/>
</TestProvider>
);
expect(screen.queryByText('Update')).not.toBeInTheDocument();
});
});
});

View File

@ -113,12 +113,17 @@ export function InstallControlsButton({
}
};
if (pluginStatus === PluginStatus.UNINSTALL) {
const disableUninstall =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? plugin.isUninstallingFromInstance
: isUninstalling;
let disableUninstall =
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
? plugin.isUninstallingFromInstance
: isUninstalling;
let uninstallTitle = '';
if (plugin.isPreinstalled.found) {
disableUninstall = true;
uninstallTitle = 'Preinstalled plugin. Remove from Grafana config before uninstalling.';
}
if (pluginStatus === PluginStatus.UNINSTALL) {
return (
<>
<ConfirmModal
@ -131,7 +136,7 @@ export function InstallControlsButton({
onDismiss={hideConfirmModal}
/>
<Stack alignItems="flex-start" width="auto" height="auto">
<Button variant="destructive" disabled={disableUninstall} onClick={showConfirmModal}>
<Button variant="destructive" disabled={disableUninstall} onClick={showConfirmModal} title={uninstallTitle}>
{uninstallBtnText}
</Button>
</Stack>
@ -152,12 +157,12 @@ export function InstallControlsButton({
return (
<Stack alignItems="flex-start" width="auto" height="auto">
{!plugin.isManaged && (
{!plugin.isManaged && !plugin.isPreinstalled.withVersion && (
<Button disabled={disableUpdate} onClick={onUpdate}>
{isInstalling ? 'Updating' : 'Update'}
</Button>
)}
<Button variant="destructive" disabled={isUninstalling} onClick={onUninstall}>
<Button variant="destructive" disabled={disableUninstall} onClick={onUninstall} title={uninstallTitle}>
{uninstallBtnText}
</Button>
</Stack>

View File

@ -58,6 +58,7 @@ describe('PluginListItem', () => {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
};
/** As Grid */

View File

@ -34,6 +34,7 @@ describe('PluginListItemBadges', () => {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
};
afterEach(() => {
@ -84,6 +85,20 @@ describe('PluginListItemBadges', () => {
expect(screen.queryByText(/update available/i)).toBeNull();
});
it('does not render an upgrade badge (when plugin is preinstalled with a version)', () => {
render(
<PluginListItemBadges
plugin={{
...plugin,
hasUpdate: true,
installedVersion: '0.0.9',
isPreinstalled: { found: true, withVersion: true },
}}
/>
);
expect(screen.queryByText(/update available/i)).toBeNull();
});
it('renders an angular badge (when plugin is angular)', () => {
render(<PluginListItemBadges plugin={{ ...plugin, angularDetected: true }} />);
expect(screen.getByText(/angular/i)).toBeVisible();

View File

@ -24,7 +24,9 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
<Stack height="auto" wrap="wrap">
<PluginEnterpriseBadge plugin={plugin} />
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && (
<PluginUpdateAvailableBadge plugin={plugin} />
)}
{plugin.angularDetected && <PluginAngularBadge />}
</Stack>
);
@ -36,7 +38,9 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
{plugin.isDeprecated && <PluginDeprecatedBadge />}
{plugin.isInstalled && <PluginInstalledBadge />}
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && (
<PluginUpdateAvailableBadge plugin={plugin} />
)}
{plugin.angularDetected && <PluginAngularBadge />}
</Stack>
);

View File

@ -204,6 +204,7 @@ describe('Plugins/Helpers', () => {
isDeprecated: false,
isPublished: true,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
name: 'Zabbix',
orgName: 'Alexander Zobnin',
popularity: 0.2111,
@ -282,6 +283,7 @@ describe('Plugins/Helpers', () => {
isPublished: false,
isDeprecated: false,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
name: 'Zabbix',
orgName: 'Alexander Zobnin',
popularity: 0,
@ -335,6 +337,7 @@ describe('Plugins/Helpers', () => {
isPublished: true,
isDeprecated: false,
isManaged: false,
isPreinstalled: { found: false, withVersion: false },
name: 'Zabbix',
orgName: 'Alexander Zobnin',
popularity: 0.2111,

View File

@ -143,6 +143,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
isInstalled: isDisabled,
isDisabled: isDisabled,
isManaged: isManagedPlugin(id),
isPreinstalled: isPreinstalledPlugin(id),
isDeprecated: status === RemotePluginStatus.Deprecated,
isCore: plugin.internal,
isDev: false,
@ -193,6 +194,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
isDev: Boolean(dev),
isEnterprise: false,
isManaged: isManagedPlugin(id),
isPreinstalled: isPreinstalledPlugin(id),
type,
error: error?.errorCode,
accessControl: accessControl,
@ -241,6 +243,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
isDeprecated: remote?.status === RemotePluginStatus.Deprecated,
isPublished: true,
isManaged: isManagedPlugin(id),
isPreinstalled: isPreinstalledPlugin(id),
// TODO<check if we would like to keep preferring the remote version>
name: remote?.name || local?.name || '',
// TODO<check if we would like to keep preferring the remote version>
@ -382,6 +385,13 @@ export function isManagedPlugin(id: string) {
return pluginCatalogManagedPlugins?.includes(id);
}
export function isPreinstalledPlugin(id: string): { found: boolean; withVersion: boolean } {
const { pluginCatalogPreinstalledPlugins } = config;
const plugin = pluginCatalogPreinstalledPlugins?.find((p) => p.id === id);
return { found: !!plugin?.id, withVersion: !!plugin?.version };
}
function isDisabledSecretsPlugin(type?: PluginType): boolean {
return type === PluginType.secretsmanager && !config.secretsManagerPluginEnabled;
}

View File

@ -25,7 +25,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
version = latestCompatibleVersion?.version;
}
if (Boolean(version)) {
if (version) {
if (plugin.isManaged) {
info.push({
label: t('plugins.details.labels.version', 'Version'),
@ -34,7 +34,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
} else {
info.push({
label: t('plugins.details.labels.version', 'Version'),
value: version,
value: `${version}${plugin.isPreinstalled.withVersion ? ' (preinstalled)' : ''}`,
});
}
}

View File

@ -326,6 +326,24 @@ describe('Plugin details page', () => {
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
});
it('should not display an update button for a plugin that is pre installed', async () => {
const { queryByRole, getByText } = renderPluginDetails({
id,
isInstalled: true,
hasUpdate: true,
isPreinstalled: { found: true, withVersion: true },
});
// Does not display an "update" button
expect(await queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
// Does not display "install" button
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
// Display an uninstall button but disabled
expect(getByText(/Uninstall/i).closest('button')).toBeDisabled();
});
it('should display an install button for enterprise plugins if license is valid', async () => {
config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };

View File

@ -40,6 +40,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
isDisabled: boolean;
isDeprecated: boolean;
isManaged: boolean; // Indicates that the plugin version is managed by Grafana
isPreinstalled: { found: boolean; withVersion: boolean }; // Indicates that the plugin is pre-installed
// `isPublished` is TRUE if the plugin is published to grafana.com
isPublished: boolean;
name: string;