mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Adapt plugin page to preinstalled plugins (#91874)
This commit is contained in:
parent
340af8cf6b
commit
44290ddf32
@ -42,6 +42,11 @@ export type AppPluginConfig = {
|
|||||||
angular: AngularMeta;
|
angular: AngularMeta;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PreinstalledPlugin = {
|
||||||
|
id: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class GrafanaBootConfig implements GrafanaConfig {
|
export class GrafanaBootConfig implements GrafanaConfig {
|
||||||
publicDashboardAccessToken?: string;
|
publicDashboardAccessToken?: string;
|
||||||
publicDashboardsEnabled = true;
|
publicDashboardsEnabled = true;
|
||||||
@ -124,6 +129,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
|||||||
pluginAdminExternalManageEnabled = false;
|
pluginAdminExternalManageEnabled = false;
|
||||||
pluginCatalogHiddenPlugins: string[] = [];
|
pluginCatalogHiddenPlugins: string[] = [];
|
||||||
pluginCatalogManagedPlugins: string[] = [];
|
pluginCatalogManagedPlugins: string[] = [];
|
||||||
|
pluginCatalogPreinstalledPlugins: PreinstalledPlugin[] = [];
|
||||||
pluginsCDNBaseURL = '';
|
pluginsCDNBaseURL = '';
|
||||||
expressionsEnabled = false;
|
expressionsEnabled = false;
|
||||||
customTheme?: undefined;
|
customTheme?: undefined;
|
||||||
|
@ -228,6 +228,7 @@ type FrontendSettingsDTO struct {
|
|||||||
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
|
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
|
||||||
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
|
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
|
||||||
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
|
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
|
||||||
|
PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"`
|
||||||
ExpressionsEnabled bool `json:"expressionsEnabled"`
|
ExpressionsEnabled bool `json:"expressionsEnabled"`
|
||||||
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
|
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
|
||||||
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`
|
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`
|
||||||
|
@ -274,6 +274,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||||||
PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
|
PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
|
||||||
PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins,
|
PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins,
|
||||||
PluginCatalogManagedPlugins: hs.managedPluginsService.ManagedPlugins(c.Req.Context()),
|
PluginCatalogManagedPlugins: hs.managedPluginsService.ManagedPlugins(c.Req.Context()),
|
||||||
|
PluginCatalogPreinstalledPlugins: hs.Cfg.InstallPlugins,
|
||||||
ExpressionsEnabled: hs.Cfg.ExpressionsEnabled,
|
ExpressionsEnabled: hs.Cfg.ExpressionsEnabled,
|
||||||
AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders,
|
AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders,
|
||||||
AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled,
|
AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled,
|
||||||
|
@ -524,8 +524,8 @@ type Cfg struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InstallPlugin struct {
|
type InstallPlugin struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
Version string
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddChangePasswordLink returns if login form is disabled or not since
|
// AddChangePasswordLink returns if login form is disabled or not since
|
||||||
|
@ -21,6 +21,7 @@ export default {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
name: 'Zabbix',
|
name: 'Zabbix',
|
||||||
orgName: 'Alexander Zobnin',
|
orgName: 'Alexander Zobnin',
|
||||||
popularity: 0.2093,
|
popularity: 0.2093,
|
||||||
|
@ -33,6 +33,7 @@ const plugin: CatalogPlugin = {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('GetStartedWithDataSource', () => {
|
describe('GetStartedWithDataSource', () => {
|
||||||
|
@ -33,6 +33,7 @@ const plugin: CatalogPlugin = {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
|
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
|
||||||
@ -253,5 +254,17 @@ describe('InstallControlsButton', () => {
|
|||||||
);
|
);
|
||||||
expect(screen.queryByText('Update')).not.toBeInTheDocument();
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -113,12 +113,17 @@ export function InstallControlsButton({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pluginStatus === PluginStatus.UNINSTALL) {
|
let disableUninstall =
|
||||||
const disableUninstall =
|
|
||||||
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
|
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
|
||||||
? plugin.isUninstallingFromInstance
|
? plugin.isUninstallingFromInstance
|
||||||
: isUninstalling;
|
: isUninstalling;
|
||||||
|
let uninstallTitle = '';
|
||||||
|
if (plugin.isPreinstalled.found) {
|
||||||
|
disableUninstall = true;
|
||||||
|
uninstallTitle = 'Preinstalled plugin. Remove from Grafana config before uninstalling.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pluginStatus === PluginStatus.UNINSTALL) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@ -131,7 +136,7 @@ export function InstallControlsButton({
|
|||||||
onDismiss={hideConfirmModal}
|
onDismiss={hideConfirmModal}
|
||||||
/>
|
/>
|
||||||
<Stack alignItems="flex-start" width="auto" height="auto">
|
<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}
|
{uninstallBtnText}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
@ -152,12 +157,12 @@ export function InstallControlsButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack alignItems="flex-start" width="auto" height="auto">
|
<Stack alignItems="flex-start" width="auto" height="auto">
|
||||||
{!plugin.isManaged && (
|
{!plugin.isManaged && !plugin.isPreinstalled.withVersion && (
|
||||||
<Button disabled={disableUpdate} onClick={onUpdate}>
|
<Button disabled={disableUpdate} onClick={onUpdate}>
|
||||||
{isInstalling ? 'Updating' : 'Update'}
|
{isInstalling ? 'Updating' : 'Update'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="destructive" disabled={isUninstalling} onClick={onUninstall}>
|
<Button variant="destructive" disabled={disableUninstall} onClick={onUninstall} title={uninstallTitle}>
|
||||||
{uninstallBtnText}
|
{uninstallBtnText}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -58,6 +58,7 @@ describe('PluginListItem', () => {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
/** As Grid */
|
/** As Grid */
|
||||||
|
@ -34,6 +34,7 @@ describe('PluginListItemBadges', () => {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -84,6 +85,20 @@ describe('PluginListItemBadges', () => {
|
|||||||
expect(screen.queryByText(/update available/i)).toBeNull();
|
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)', () => {
|
it('renders an angular badge (when plugin is angular)', () => {
|
||||||
render(<PluginListItemBadges plugin={{ ...plugin, angularDetected: true }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, angularDetected: true }} />);
|
||||||
expect(screen.getByText(/angular/i)).toBeVisible();
|
expect(screen.getByText(/angular/i)).toBeVisible();
|
||||||
|
@ -24,7 +24,9 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
|||||||
<Stack height="auto" wrap="wrap">
|
<Stack height="auto" wrap="wrap">
|
||||||
<PluginEnterpriseBadge plugin={plugin} />
|
<PluginEnterpriseBadge plugin={plugin} />
|
||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
|
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && (
|
||||||
|
<PluginUpdateAvailableBadge plugin={plugin} />
|
||||||
|
)}
|
||||||
{plugin.angularDetected && <PluginAngularBadge />}
|
{plugin.angularDetected && <PluginAngularBadge />}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@ -36,7 +38,9 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
|||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
{plugin.isDeprecated && <PluginDeprecatedBadge />}
|
{plugin.isDeprecated && <PluginDeprecatedBadge />}
|
||||||
{plugin.isInstalled && <PluginInstalledBadge />}
|
{plugin.isInstalled && <PluginInstalledBadge />}
|
||||||
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
|
{hasUpdate && !plugin.isManaged && !plugin.isPreinstalled.withVersion && (
|
||||||
|
<PluginUpdateAvailableBadge plugin={plugin} />
|
||||||
|
)}
|
||||||
{plugin.angularDetected && <PluginAngularBadge />}
|
{plugin.angularDetected && <PluginAngularBadge />}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
@ -204,6 +204,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
name: 'Zabbix',
|
name: 'Zabbix',
|
||||||
orgName: 'Alexander Zobnin',
|
orgName: 'Alexander Zobnin',
|
||||||
popularity: 0.2111,
|
popularity: 0.2111,
|
||||||
@ -282,6 +283,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
isPublished: false,
|
isPublished: false,
|
||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
name: 'Zabbix',
|
name: 'Zabbix',
|
||||||
orgName: 'Alexander Zobnin',
|
orgName: 'Alexander Zobnin',
|
||||||
popularity: 0,
|
popularity: 0,
|
||||||
@ -335,6 +337,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
isPublished: true,
|
isPublished: true,
|
||||||
isDeprecated: false,
|
isDeprecated: false,
|
||||||
isManaged: false,
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
name: 'Zabbix',
|
name: 'Zabbix',
|
||||||
orgName: 'Alexander Zobnin',
|
orgName: 'Alexander Zobnin',
|
||||||
popularity: 0.2111,
|
popularity: 0.2111,
|
||||||
|
@ -143,6 +143,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
|
|||||||
isInstalled: isDisabled,
|
isInstalled: isDisabled,
|
||||||
isDisabled: isDisabled,
|
isDisabled: isDisabled,
|
||||||
isManaged: isManagedPlugin(id),
|
isManaged: isManagedPlugin(id),
|
||||||
|
isPreinstalled: isPreinstalledPlugin(id),
|
||||||
isDeprecated: status === RemotePluginStatus.Deprecated,
|
isDeprecated: status === RemotePluginStatus.Deprecated,
|
||||||
isCore: plugin.internal,
|
isCore: plugin.internal,
|
||||||
isDev: false,
|
isDev: false,
|
||||||
@ -193,6 +194,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
|||||||
isDev: Boolean(dev),
|
isDev: Boolean(dev),
|
||||||
isEnterprise: false,
|
isEnterprise: false,
|
||||||
isManaged: isManagedPlugin(id),
|
isManaged: isManagedPlugin(id),
|
||||||
|
isPreinstalled: isPreinstalledPlugin(id),
|
||||||
type,
|
type,
|
||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
accessControl: accessControl,
|
accessControl: accessControl,
|
||||||
@ -241,6 +243,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
|
|||||||
isDeprecated: remote?.status === RemotePluginStatus.Deprecated,
|
isDeprecated: remote?.status === RemotePluginStatus.Deprecated,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isManaged: isManagedPlugin(id),
|
isManaged: isManagedPlugin(id),
|
||||||
|
isPreinstalled: isPreinstalledPlugin(id),
|
||||||
// TODO<check if we would like to keep preferring the remote version>
|
// TODO<check if we would like to keep preferring the remote version>
|
||||||
name: remote?.name || local?.name || '',
|
name: remote?.name || local?.name || '',
|
||||||
// TODO<check if we would like to keep preferring the remote version>
|
// 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);
|
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 {
|
function isDisabledSecretsPlugin(type?: PluginType): boolean {
|
||||||
return type === PluginType.secretsmanager && !config.secretsManagerPluginEnabled;
|
return type === PluginType.secretsmanager && !config.secretsManagerPluginEnabled;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
|
|||||||
version = latestCompatibleVersion?.version;
|
version = latestCompatibleVersion?.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Boolean(version)) {
|
if (version) {
|
||||||
if (plugin.isManaged) {
|
if (plugin.isManaged) {
|
||||||
info.push({
|
info.push({
|
||||||
label: t('plugins.details.labels.version', 'Version'),
|
label: t('plugins.details.labels.version', 'Version'),
|
||||||
@ -34,7 +34,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
|
|||||||
} else {
|
} else {
|
||||||
info.push({
|
info.push({
|
||||||
label: t('plugins.details.labels.version', 'Version'),
|
label: t('plugins.details.labels.version', 'Version'),
|
||||||
value: version,
|
value: `${version}${plugin.isPreinstalled.withVersion ? ' (preinstalled)' : ''}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,6 +326,24 @@ describe('Plugin details page', () => {
|
|||||||
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
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 () => {
|
it('should display an install button for enterprise plugins if license is valid', async () => {
|
||||||
config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };
|
config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
|
|||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
isDeprecated: boolean;
|
isDeprecated: boolean;
|
||||||
isManaged: boolean; // Indicates that the plugin version is managed by Grafana
|
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` is TRUE if the plugin is published to grafana.com
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user