mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Hide version information when plugin is managed (#88065)
* first pass * fixup * fix linter issues * fix API test * update naming * rework * update var name * empty check * prettier * fix test * fix lint
This commit is contained in:
parent
e2ee7f06eb
commit
1b3fa8c47f
@ -122,6 +122,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
pluginAdminEnabled = true;
|
||||
pluginAdminExternalManageEnabled = false;
|
||||
pluginCatalogHiddenPlugins: string[] = [];
|
||||
pluginCatalogManagedPlugins: string[] = [];
|
||||
pluginsCDNBaseURL = '';
|
||||
expressionsEnabled = false;
|
||||
customTheme?: undefined;
|
||||
|
@ -227,6 +227,7 @@ type FrontendSettingsDTO struct {
|
||||
PluginAdminEnabled bool `json:"pluginAdminEnabled"`
|
||||
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
|
||||
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
|
||||
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
|
||||
ExpressionsEnabled bool `json:"expressionsEnabled"`
|
||||
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
|
||||
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`
|
||||
|
@ -266,6 +266,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
PluginAdminEnabled: hs.Cfg.PluginAdminEnabled,
|
||||
PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
|
||||
PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins,
|
||||
PluginCatalogManagedPlugins: hs.managedPluginsService.ManagedPlugins(c.Req.Context()),
|
||||
ExpressionsEnabled: hs.Cfg.ExpressionsEnabled,
|
||||
AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders,
|
||||
AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled,
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
@ -77,8 +78,9 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
||||
PluginsCDNURLTemplate: cfg.PluginsCDNURLTemplate,
|
||||
PluginSettings: cfg.PluginSettings,
|
||||
}),
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
SocialService: socialimpl.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage(), nil, &ssosettingstests.MockService{}),
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
SocialService: socialimpl.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage(), nil, &ssosettingstests.MockService{}),
|
||||
managedPluginsService: managedplugins.NewNoop(),
|
||||
}
|
||||
|
||||
m := web.New()
|
||||
|
@ -25,10 +25,6 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/anonymous"
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
httpstatic "github.com/grafana/grafana/pkg/api/static"
|
||||
@ -48,7 +44,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/anonymous"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/cleanup"
|
||||
@ -78,6 +77,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
@ -195,6 +195,7 @@ type HTTPServer struct {
|
||||
apiKeyService apikey.Service
|
||||
kvStore kvstore.KVStore
|
||||
pluginsCDNService *pluginscdn.Service
|
||||
managedPluginsService managedplugins.Manager
|
||||
|
||||
userService user.Service
|
||||
tempUserService tempUser.Service
|
||||
@ -254,7 +255,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service,
|
||||
folderPermissionsService accesscontrol.FolderPermissionsService,
|
||||
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
|
||||
starService star.Service, csrfService csrf.Service,
|
||||
starService star.Service, csrfService csrf.Service, managedPlugins managedplugins.Manager,
|
||||
playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore,
|
||||
secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service,
|
||||
secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
|
||||
@ -359,6 +360,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
statsService: statsService,
|
||||
authnService: authnService,
|
||||
pluginsCDNService: pluginsCDNService,
|
||||
managedPluginsService: managedPlugins,
|
||||
starApi: starApi,
|
||||
promRegister: promRegister,
|
||||
promGatherer: promGatherer,
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
@ -39,6 +39,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
@ -99,6 +100,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
ID: pluginID,
|
||||
},
|
||||
})
|
||||
hs.managedPluginsService = managedplugins.NewNoop()
|
||||
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: tc.permissionOrg,
|
||||
@ -641,6 +643,7 @@ func Test_PluginsList_AccessControl(t *testing.T) {
|
||||
hs.PluginSettings = &pluginSettings
|
||||
hs.pluginStore = pluginstore.New(pluginRegistry, &fakes.FakeLoader{})
|
||||
hs.pluginFileStore = filestore.ProvideService(pluginRegistry)
|
||||
hs.managedPluginsService = managedplugins.NewNoop()
|
||||
var err error
|
||||
hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest())
|
||||
require.NoError(t, err)
|
||||
|
19
pkg/services/pluginsintegration/managedplugins/managed.go
Normal file
19
pkg/services/pluginsintegration/managedplugins/managed.go
Normal file
@ -0,0 +1,19 @@
|
||||
package managedplugins
|
||||
|
||||
import "context"
|
||||
|
||||
type Manager interface {
|
||||
ManagedPlugins(ctx context.Context) []string
|
||||
}
|
||||
|
||||
var _ Manager = (*Noop)(nil)
|
||||
|
||||
type Noop struct{}
|
||||
|
||||
func NewNoop() *Noop {
|
||||
return &Noop{}
|
||||
}
|
||||
|
||||
func (s *Noop) ManagedPlugins(_ context.Context) []string {
|
||||
return []string{}
|
||||
}
|
@ -41,6 +41,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/keystore"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/loader"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
@ -132,10 +133,12 @@ var WireExtensionSet = wire.NewSet(
|
||||
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
||||
signature.ProvideOSSAuthorizer,
|
||||
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
|
||||
wire.Bind(new(finder.Finder), new(*finder.Local)),
|
||||
finder.ProvideLocalFinder,
|
||||
wire.Bind(new(finder.Finder), new(*finder.Local)),
|
||||
ProvideClientDecorator,
|
||||
wire.Bind(new(plugins.Client), new(*client.Decorator)),
|
||||
managedplugins.NewNoop,
|
||||
wire.Bind(new(managedplugins.Manager), new(*managedplugins.Noop)),
|
||||
)
|
||||
|
||||
func ProvideClientDecorator(
|
||||
|
@ -74,7 +74,6 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin {
|
||||
Module: p.Module,
|
||||
BaseURL: p.BaseURL,
|
||||
ExternalService: p.ExternalService,
|
||||
|
||||
Angular: p.Angular,
|
||||
Angular: p.Angular,
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export default {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
name: 'Zabbix',
|
||||
orgName: 'Alexander Zobnin',
|
||||
popularity: 0.2093,
|
||||
|
@ -32,6 +32,7 @@ const plugin: CatalogPlugin = {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
};
|
||||
|
||||
describe('GetStartedWithDataSource', () => {
|
||||
|
@ -32,6 +32,7 @@ const plugin: CatalogPlugin = {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
};
|
||||
|
||||
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
|
||||
@ -242,4 +243,15 @@ describe('InstallControlsButton', () => {
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('update button', () => {
|
||||
it('should be hidden when plugin is managed', () => {
|
||||
render(
|
||||
<TestProvider>
|
||||
<InstallControlsButton plugin={{ ...plugin, isManaged: true }} pluginStatus={PluginStatus.UPDATE} />
|
||||
</TestProvider>
|
||||
);
|
||||
expect(screen.queryByText('Update')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -152,9 +152,11 @@ export function InstallControlsButton({
|
||||
|
||||
return (
|
||||
<Stack alignItems="flex-start" width="auto" height="auto">
|
||||
<Button disabled={disableUpdate} onClick={onUpdate}>
|
||||
{isInstalling ? 'Updating' : 'Update'}
|
||||
</Button>
|
||||
{!plugin.isManaged && (
|
||||
<Button disabled={disableUpdate} onClick={onUpdate}>
|
||||
{isInstalling ? 'Updating' : 'Update'}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="destructive" disabled={isUninstalling} onClick={onUninstall}>
|
||||
{uninstallBtnText}
|
||||
</Button>
|
||||
|
@ -47,6 +47,7 @@ const getMockPlugin = (id: string): CatalogPlugin => {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -57,6 +57,7 @@ describe('PluginListItem', () => {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
};
|
||||
|
||||
/** As Grid */
|
||||
|
@ -33,6 +33,7 @@ describe('PluginListItemBadges', () => {
|
||||
isDisabled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@ -76,6 +77,13 @@ describe('PluginListItemBadges', () => {
|
||||
expect(screen.getByText(/update available/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('does not render an upgrade badge (when plugin has an available update and is managed)', () => {
|
||||
render(
|
||||
<PluginListItemBadges plugin={{ ...plugin, hasUpdate: true, installedVersion: '0.0.9', isManaged: 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();
|
||||
|
@ -24,7 +24,7 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
||||
<Stack height="auto" wrap="wrap">
|
||||
<PluginEnterpriseBadge plugin={plugin} />
|
||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||
{hasUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||
{plugin.angularDetected && <PluginAngularBadge />}
|
||||
</Stack>
|
||||
);
|
||||
@ -36,7 +36,7 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||
{plugin.isDeprecated && <PluginDeprecatedBadge />}
|
||||
{plugin.isInstalled && <PluginInstalledBadge />}
|
||||
{hasUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||
{hasUpdate && !plugin.isManaged && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||
{plugin.angularDetected && <PluginAngularBadge />}
|
||||
</Stack>
|
||||
);
|
||||
|
@ -203,6 +203,7 @@ describe('Plugins/Helpers', () => {
|
||||
isInstalled: false,
|
||||
isDeprecated: false,
|
||||
isPublished: true,
|
||||
isManaged: false,
|
||||
name: 'Zabbix',
|
||||
orgName: 'Alexander Zobnin',
|
||||
popularity: 0.2111,
|
||||
@ -280,6 +281,7 @@ describe('Plugins/Helpers', () => {
|
||||
isInstalled: true,
|
||||
isPublished: false,
|
||||
isDeprecated: false,
|
||||
isManaged: false,
|
||||
name: 'Zabbix',
|
||||
orgName: 'Alexander Zobnin',
|
||||
popularity: 0,
|
||||
@ -332,6 +334,7 @@ describe('Plugins/Helpers', () => {
|
||||
isInstalled: true,
|
||||
isPublished: true,
|
||||
isDeprecated: false,
|
||||
isManaged: false,
|
||||
name: 'Zabbix',
|
||||
orgName: 'Alexander Zobnin',
|
||||
popularity: 0.2111,
|
||||
|
@ -142,6 +142,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
|
||||
isPublished: true,
|
||||
isInstalled: isDisabled,
|
||||
isDisabled: isDisabled,
|
||||
isManaged: isManagedPlugin(id),
|
||||
isDeprecated: status === RemotePluginStatus.Deprecated,
|
||||
isCore: plugin.internal,
|
||||
isDev: false,
|
||||
@ -191,6 +192,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
||||
isDeprecated: false,
|
||||
isDev: Boolean(dev),
|
||||
isEnterprise: false,
|
||||
isManaged: isManagedPlugin(id),
|
||||
type,
|
||||
error: error?.errorCode,
|
||||
accessControl: accessControl,
|
||||
@ -238,6 +240,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
|
||||
isDisabled: isDisabled,
|
||||
isDeprecated: remote?.status === RemotePluginStatus.Deprecated,
|
||||
isPublished: true,
|
||||
isManaged: isManagedPlugin(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>
|
||||
@ -373,6 +376,12 @@ function isNotHiddenByConfig(id: string) {
|
||||
return !pluginCatalogHiddenPlugins.includes(id);
|
||||
}
|
||||
|
||||
export function isManagedPlugin(id: string) {
|
||||
const { pluginCatalogManagedPlugins }: { pluginCatalogManagedPlugins: string[] } = config;
|
||||
|
||||
return pluginCatalogManagedPlugins?.includes(id);
|
||||
}
|
||||
|
||||
function isDisabledSecretsPlugin(type?: PluginType): boolean {
|
||||
return type === PluginType.secretsmanager && !config.secretsManagerPluginEnabled;
|
||||
}
|
||||
|
@ -25,10 +25,17 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => {
|
||||
}
|
||||
|
||||
if (Boolean(version)) {
|
||||
info.push({
|
||||
label: 'Version',
|
||||
value: version,
|
||||
});
|
||||
if (plugin.isManaged) {
|
||||
info.push({
|
||||
label: 'Version',
|
||||
value: 'Managed by Grafana',
|
||||
});
|
||||
} else {
|
||||
info.push({
|
||||
label: 'Version',
|
||||
value: version,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Boolean(plugin.orgName)) {
|
||||
|
@ -315,6 +315,17 @@ describe('Plugin details page', () => {
|
||||
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display an update button for a plugin that is managed', async () => {
|
||||
const { queryByRole } = renderPluginDetails({ id, isInstalled: true, hasUpdate: true, isManaged: true });
|
||||
|
||||
// Does not display an "update" button
|
||||
expect(await queryByRole('button', { name: /update/i })).not.toBeInTheDocument();
|
||||
expect(queryByRole('button', { name: /uninstall/i })).toBeInTheDocument();
|
||||
|
||||
// Does not display "install" button
|
||||
expect(queryByRole('button', { name: /^install/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display an install button for enterprise plugins if license is valid', async () => {
|
||||
config.licenseInfo.enabledFeatures = { 'enterprise.plugins': true };
|
||||
|
||||
|
@ -44,6 +44,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
|
||||
isInstalled: boolean;
|
||||
isDisabled: boolean;
|
||||
isDeprecated: boolean;
|
||||
isManaged: boolean; // Indicates that the plugin version is managed by Grafana
|
||||
// `isPublished` is TRUE if the plugin is published to grafana.com
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
|
Loading…
Reference in New Issue
Block a user