mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Change managed plugins installation call (#77120)
This commit is contained in:
committed by
GitHub
parent
6097ff255c
commit
e754c5a6c6
@@ -392,7 +392,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
apiRoute.Any("/plugin-proxy/:pluginId/*", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest)
|
apiRoute.Any("/plugin-proxy/:pluginId/*", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest)
|
||||||
apiRoute.Any("/plugin-proxy/:pluginId", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest)
|
apiRoute.Any("/plugin-proxy/:pluginId", requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), authorize(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginIDScope)), hs.ProxyPluginRequest)
|
||||||
|
|
||||||
if hs.Cfg.PluginAdminEnabled && !hs.Cfg.PluginAdminExternalManageEnabled {
|
if hs.Cfg.PluginAdminEnabled && (hs.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagManagedPluginsInstall) || !hs.Cfg.PluginAdminExternalManageEnabled) {
|
||||||
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
|
||||||
pluginRoute.Post("/:pluginId/install", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
|
pluginRoute.Post("/:pluginId/install", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.InstallPlugin))
|
||||||
pluginRoute.Post("/:pluginId/uninstall", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
|
pluginRoute.Post("/:pluginId/uninstall", authorize(ac.EvalPermission(pluginaccesscontrol.ActionInstall)), routing.Wrap(hs.UninstallPlugin))
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
|||||||
hs.Cfg = &setting.Cfg{
|
hs.Cfg = &setting.Cfg{
|
||||||
PluginAdminEnabled: tc.pluginAdminEnabled,
|
PluginAdminEnabled: tc.pluginAdminEnabled,
|
||||||
PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled}
|
PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled}
|
||||||
|
hs.Cfg.IsFeatureToggleEnabled = func(_ string) bool { return false }
|
||||||
|
|
||||||
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
||||||
hs.pluginInstaller = NewFakePluginInstaller()
|
hs.pluginInstaller = NewFakePluginInstaller()
|
||||||
hs.pluginFileStore = &fakes.FakePluginFileStore{}
|
hs.pluginFileStore = &fakes.FakePluginFileStore{}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package pluginaccesscontrol
|
|||||||
import (
|
import (
|
||||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@@ -67,7 +68,8 @@ func DeclareRBACRoles(service ac.Service, cfg *setting.Cfg) error {
|
|||||||
Grants: []string{ac.RoleGrafanaAdmin},
|
Grants: []string{ac.RoleGrafanaAdmin},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.PluginAdminEnabled || cfg.PluginAdminExternalManageEnabled {
|
if !cfg.PluginAdminEnabled ||
|
||||||
|
(cfg.PluginAdminExternalManageEnabled && !cfg.IsFeatureToggleEnabled(featuremgmt.FlagManagedPluginsInstall)) {
|
||||||
PluginsMaintainer.Grants = []string{}
|
PluginsMaintainer.Grants = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { PluginError, PluginMeta, renderMarkdown } from '@grafana/data';
|
|||||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||||
|
|
||||||
import { API_ROOT, GCOM_API_ROOT } from './constants';
|
import { API_ROOT, GCOM_API_ROOT, INSTANCE_API_ROOT } from './constants';
|
||||||
import { isLocalPluginVisibleByConfig, isRemotePluginVisibleByConfig } from './helpers';
|
import { isLocalPluginVisibleByConfig, isRemotePluginVisibleByConfig } from './helpers';
|
||||||
import { LocalPlugin, RemotePlugin, CatalogPluginDetails, Version, PluginVersion } from './types';
|
import { LocalPlugin, RemotePlugin, CatalogPluginDetails, Version, PluginVersion, InstancePlugin } from './types';
|
||||||
|
|
||||||
export async function getPluginDetails(id: string): Promise<CatalogPluginDetails> {
|
export async function getPluginDetails(id: string): Promise<CatalogPluginDetails> {
|
||||||
const remote = await getRemotePlugin(id);
|
const remote = await getRemotePlugin(id);
|
||||||
@@ -105,6 +105,14 @@ export async function getLocalPlugins(): Promise<LocalPlugin[]> {
|
|||||||
return localPlugins.filter(isLocalPluginVisibleByConfig);
|
return localPlugins.filter(isLocalPluginVisibleByConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getInstancePlugins(): Promise<InstancePlugin[]> {
|
||||||
|
const { items: instancePlugins }: { items: InstancePlugin[] } = await getBackendSrv().get(
|
||||||
|
`${INSTANCE_API_ROOT}/plugins`
|
||||||
|
);
|
||||||
|
|
||||||
|
return instancePlugins;
|
||||||
|
}
|
||||||
|
|
||||||
export async function installPlugin(id: string) {
|
export async function installPlugin(id: string) {
|
||||||
// This will install the latest compatible version based on the logic
|
// This will install the latest compatible version based on the logic
|
||||||
// on the backend.
|
// on the backend.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { AppEvents } from '@grafana/data';
|
|||||||
import { config, locationService } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui';
|
import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
|
import configCore from 'app/core/config';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
import { removePluginFromNavTree } from 'app/core/reducers/navBarTree';
|
import { removePluginFromNavTree } from 'app/core/reducers/navBarTree';
|
||||||
import { useDispatch } from 'app/types';
|
import { useDispatch } from 'app/types';
|
||||||
@@ -65,13 +66,18 @@ export function InstallControlsButton({
|
|||||||
const onInstall = async () => {
|
const onInstall = async () => {
|
||||||
trackPluginInstalled(trackingProps);
|
trackPluginInstalled(trackingProps);
|
||||||
const result = await install(plugin.id, latestCompatibleVersion?.version);
|
const result = await install(plugin.id, latestCompatibleVersion?.version);
|
||||||
// refresh the store to have the new installed plugin
|
|
||||||
await fetchDetails(plugin.id);
|
|
||||||
if (!errorInstalling && !('error' in result)) {
|
if (!errorInstalling && !('error' in result)) {
|
||||||
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
|
let successMessage = `Installed ${plugin.name}`;
|
||||||
|
if (config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall) {
|
||||||
|
successMessage = 'Install requested, this may take a few minutes.';
|
||||||
|
}
|
||||||
|
|
||||||
|
appEvents.emit(AppEvents.alertSuccess, [successMessage]);
|
||||||
if (plugin.type === 'app') {
|
if (plugin.type === 'app') {
|
||||||
setNeedReload?.(true);
|
setNeedReload?.(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await fetchDetails(plugin.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React, { useState } from 'react';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import { HorizontalGroup, Icon, useStyles2, VerticalGroup } from '@grafana/ui';
|
import { HorizontalGroup, Icon, useStyles2, VerticalGroup } from '@grafana/ui';
|
||||||
|
import configCore from 'app/core/config';
|
||||||
|
|
||||||
import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin';
|
import { GetStartedWithPlugin } from '../components/GetStartedWithPlugin';
|
||||||
import { InstallControlsButton } from '../components/InstallControls';
|
import { InstallControlsButton } from '../components/InstallControls';
|
||||||
@@ -40,7 +41,7 @@ export const PluginActions = ({ plugin }: Props) => {
|
|||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
{!isInstallControlsDisabled && (
|
{!isInstallControlsDisabled && (
|
||||||
<>
|
<>
|
||||||
{isExternallyManaged && !hasInstallWarning ? (
|
{isExternallyManaged && !hasInstallWarning && !configCore.featureToggles.managedPluginsInstall ? (
|
||||||
<ExternallyManagedButton
|
<ExternallyManagedButton
|
||||||
pluginId={plugin.id}
|
pluginId={plugin.id}
|
||||||
pluginStatus={pluginStatus}
|
pluginStatus={pluginStatus}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export const API_ROOT = '/api/plugins';
|
export const API_ROOT = '/api/plugins';
|
||||||
|
export const INSTANCE_API_ROOT = '/api/instance';
|
||||||
export const GCOM_API_ROOT = '/api/gnet';
|
export const GCOM_API_ROOT = '/api/gnet';
|
||||||
|
|
||||||
// Used for prefixing the Redux actions
|
// Used for prefixing the Redux actions
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
test('adds all available plugins only once', () => {
|
test('adds all available plugins only once', () => {
|
||||||
const merged = mergeLocalsAndRemotes(localPlugins, remotePlugins);
|
const merged = mergeLocalsAndRemotes({ local: localPlugins, remote: remotePlugins });
|
||||||
const mergedIds = merged.map(({ id }) => id);
|
const mergedIds = merged.map(({ id }) => id);
|
||||||
|
|
||||||
expect(merged.length).toBe(4);
|
expect(merged.length).toBe(4);
|
||||||
@@ -48,7 +48,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('merges all plugins with their counterpart (if available)', () => {
|
test('merges all plugins with their counterpart (if available)', () => {
|
||||||
const merged = mergeLocalsAndRemotes(localPlugins, remotePlugins);
|
const merged = mergeLocalsAndRemotes({ local: localPlugins, remote: remotePlugins });
|
||||||
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
||||||
|
|
||||||
// Both local & remote counterparts
|
// Both local & remote counterparts
|
||||||
@@ -67,10 +67,10 @@ describe('Plugins/Helpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('skips deprecated plugins unless they have a local - installed - counterpart', () => {
|
test('skips deprecated plugins unless they have a local - installed - counterpart', () => {
|
||||||
const merged = mergeLocalsAndRemotes(localPlugins, [
|
const merged = mergeLocalsAndRemotes({
|
||||||
...remotePlugins,
|
local: localPlugins,
|
||||||
getRemotePluginMock({ slug: 'plugin-5', status: RemotePluginStatus.Deprecated }),
|
remote: [...remotePlugins, getRemotePluginMock({ slug: 'plugin-5', status: RemotePluginStatus.Deprecated })],
|
||||||
]);
|
});
|
||||||
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
||||||
|
|
||||||
expect(merged).toHaveLength(4);
|
expect(merged).toHaveLength(4);
|
||||||
@@ -78,10 +78,10 @@ describe('Plugins/Helpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('keeps deprecated plugins in case they have a local counterpart', () => {
|
test('keeps deprecated plugins in case they have a local counterpart', () => {
|
||||||
const merged = mergeLocalsAndRemotes(
|
const merged = mergeLocalsAndRemotes({
|
||||||
[...localPlugins, getLocalPluginMock({ id: 'plugin-5' })],
|
local: [...localPlugins, getLocalPluginMock({ id: 'plugin-5' })],
|
||||||
[...remotePlugins, getRemotePluginMock({ slug: 'plugin-5', status: RemotePluginStatus.Deprecated })]
|
remote: [...remotePlugins, getRemotePluginMock({ slug: 'plugin-5', status: RemotePluginStatus.Deprecated })],
|
||||||
);
|
});
|
||||||
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId);
|
||||||
|
|
||||||
expect(merged).toHaveLength(5);
|
expect(merged).toHaveLength(5);
|
||||||
@@ -132,6 +132,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
signature: 'valid',
|
signature: 'valid',
|
||||||
type: 'app',
|
type: 'app',
|
||||||
updatedAt: '2021-05-18T14:53:01.000Z',
|
updatedAt: '2021-05-18T14:53:01.000Z',
|
||||||
|
isFullyInstalled: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,6 +211,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
type: 'app',
|
type: 'app',
|
||||||
updatedAt: '2021-08-25',
|
updatedAt: '2021-08-25',
|
||||||
installedVersion: '4.2.2',
|
installedVersion: '4.2.2',
|
||||||
|
isFullyInstalled: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,6 +261,7 @@ describe('Plugins/Helpers', () => {
|
|||||||
type: 'app',
|
type: 'app',
|
||||||
updatedAt: '2021-05-18T14:53:01.000Z',
|
updatedAt: '2021-05-18T14:53:01.000Z',
|
||||||
installedVersion: '4.2.2',
|
installedVersion: '4.2.2',
|
||||||
|
isFullyInstalled: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
import { PluginSignatureStatus, dateTimeParse, PluginError, PluginType, PluginErrorCode } from '@grafana/data';
|
import { PluginSignatureStatus, dateTimeParse, PluginError, PluginType, PluginErrorCode } from '@grafana/data';
|
||||||
import { config, featureEnabled } from '@grafana/runtime';
|
import { config, featureEnabled } from '@grafana/runtime';
|
||||||
import { Settings } from 'app/core/config';
|
import configCore, { Settings } from 'app/core/config';
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
|
|
||||||
import { CatalogPlugin, LocalPlugin, RemotePlugin, RemotePluginStatus, Version } from './types';
|
import { CatalogPlugin, InstancePlugin, LocalPlugin, RemotePlugin, RemotePluginStatus, Version } from './types';
|
||||||
|
|
||||||
export function mergeLocalsAndRemotes(
|
export function mergeLocalsAndRemotes({
|
||||||
local: LocalPlugin[] = [],
|
local = [],
|
||||||
remote: RemotePlugin[] = [],
|
remote = [],
|
||||||
errors?: PluginError[]
|
instance = [],
|
||||||
): CatalogPlugin[] {
|
pluginErrors: errors,
|
||||||
|
}: {
|
||||||
|
local: LocalPlugin[];
|
||||||
|
remote?: RemotePlugin[];
|
||||||
|
instance?: InstancePlugin[];
|
||||||
|
pluginErrors?: PluginError[];
|
||||||
|
}): CatalogPlugin[] {
|
||||||
const catalogPlugins: CatalogPlugin[] = [];
|
const catalogPlugins: CatalogPlugin[] = [];
|
||||||
const errorByPluginId = groupErrorsByPluginId(errors);
|
const errorByPluginId = groupErrorsByPluginId(errors);
|
||||||
|
|
||||||
|
const instancesSet = instance.reduce((set, instancePlugin) => {
|
||||||
|
set.add(instancePlugin.pluginSlug);
|
||||||
|
return set;
|
||||||
|
}, new Set<string>());
|
||||||
|
|
||||||
// add locals
|
// add locals
|
||||||
local.forEach((localPlugin) => {
|
local.forEach((localPlugin) => {
|
||||||
const remoteCounterpart = remote.find((r) => r.slug === localPlugin.id);
|
const remoteCounterpart = remote.find((r) => r.slug === localPlugin.id);
|
||||||
@@ -32,7 +43,15 @@ export function mergeLocalsAndRemotes(
|
|||||||
const shouldSkip = remotePlugin.status === RemotePluginStatus.Deprecated && !localCounterpart; // We are only listing deprecated plugins in case they are installed.
|
const shouldSkip = remotePlugin.status === RemotePluginStatus.Deprecated && !localCounterpart; // We are only listing deprecated plugins in case they are installed.
|
||||||
|
|
||||||
if (!shouldSkip) {
|
if (!shouldSkip) {
|
||||||
catalogPlugins.push(mergeLocalAndRemote(localCounterpart, remotePlugin, error));
|
const catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error);
|
||||||
|
|
||||||
|
// for managed instances, check if plugin is installed, but not yet present in the current instance
|
||||||
|
if (configCore.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled) {
|
||||||
|
catalogPlugin.isFullyInstalled = instancesSet.has(remotePlugin.slug) && catalogPlugin.isInstalled;
|
||||||
|
catalogPlugin.isInstalled = instancesSet.has(remotePlugin.slug) || catalogPlugin.isInstalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogPlugins.push(catalogPlugin);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,6 +114,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
|
|||||||
type: typeCode,
|
type: typeCode,
|
||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
angularDetected,
|
angularDetected,
|
||||||
|
isFullyInstalled: isDisabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +160,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
|||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
accessControl: accessControl,
|
accessControl: accessControl,
|
||||||
angularDetected,
|
angularDetected,
|
||||||
|
isFullyInstalled: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +217,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
|
|||||||
// Only local plugins have access control metadata
|
// Only local plugins have access control metadata
|
||||||
accessControl: local?.accessControl,
|
accessControl: local?.accessControl,
|
||||||
angularDetected: local?.angularDetected || remote?.angularDetected,
|
angularDetected: local?.angularDetected || remote?.angularDetected,
|
||||||
|
isFullyInstalled: Boolean(local) || isDisabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const usePluginConfig = (plugin?: CatalogPlugin) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.isInstalled && !plugin.isDisabled) {
|
if (plugin.isFullyInstalled && !plugin.isDisabled) {
|
||||||
return loadPlugin(plugin.id);
|
return loadPlugin(plugin.id);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit';
|
import { createAction, createAsyncThunk, Update } from '@reduxjs/toolkit';
|
||||||
import { from, forkJoin, timeout, lastValueFrom, catchError, throwError } from 'rxjs';
|
import { from, forkJoin, timeout, lastValueFrom, catchError, throwError, of } from 'rxjs';
|
||||||
|
|
||||||
import { PanelPlugin, PluginError } from '@grafana/data';
|
import { PanelPlugin, PluginError } from '@grafana/data';
|
||||||
import { getBackendSrv, isFetchError } from '@grafana/runtime';
|
import { config, getBackendSrv, isFetchError } from '@grafana/runtime';
|
||||||
|
import configCore from 'app/core/config';
|
||||||
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
import { importPanelPlugin } from 'app/features/plugins/importPanelPlugin';
|
||||||
import { StoreState, ThunkResult } from 'app/types';
|
import { StoreState, ThunkResult } from 'app/types';
|
||||||
|
|
||||||
@@ -14,10 +15,11 @@ import {
|
|||||||
getPluginDetails,
|
getPluginDetails,
|
||||||
installPlugin,
|
installPlugin,
|
||||||
uninstallPlugin,
|
uninstallPlugin,
|
||||||
|
getInstancePlugins,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { STATE_PREFIX } from '../constants';
|
import { STATE_PREFIX } from '../constants';
|
||||||
import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers';
|
import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers';
|
||||||
import { CatalogPlugin, RemotePlugin, LocalPlugin } from '../types';
|
import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin } from '../types';
|
||||||
|
|
||||||
// Fetches
|
// Fetches
|
||||||
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
|
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
|
||||||
@@ -27,11 +29,16 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
|
|||||||
|
|
||||||
const local$ = from(getLocalPlugins());
|
const local$ = from(getLocalPlugins());
|
||||||
const remote$ = from(getRemotePlugins());
|
const remote$ = from(getRemotePlugins());
|
||||||
|
const instance$ =
|
||||||
|
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
|
||||||
|
? from(getInstancePlugins())
|
||||||
|
: of(undefined);
|
||||||
const pluginErrors$ = from(getPluginErrors());
|
const pluginErrors$ = from(getPluginErrors());
|
||||||
|
|
||||||
forkJoin({
|
forkJoin({
|
||||||
local: local$,
|
local: local$,
|
||||||
remote: remote$,
|
remote: remote$,
|
||||||
|
instance: instance$,
|
||||||
pluginErrors: pluginErrors$,
|
pluginErrors: pluginErrors$,
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -55,9 +62,10 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
|
|||||||
|
|
||||||
if (remote.length > 0) {
|
if (remote.length > 0) {
|
||||||
const local = await lastValueFrom(local$);
|
const local = await lastValueFrom(local$);
|
||||||
|
const instance = await lastValueFrom(instance$);
|
||||||
const pluginErrors = await lastValueFrom(pluginErrors$);
|
const pluginErrors = await lastValueFrom(pluginErrors$);
|
||||||
|
|
||||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, remote, pluginErrors)));
|
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors })));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,21 +77,23 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
|
|||||||
({
|
({
|
||||||
local,
|
local,
|
||||||
remote,
|
remote,
|
||||||
|
instance,
|
||||||
pluginErrors,
|
pluginErrors,
|
||||||
}: {
|
}: {
|
||||||
local: LocalPlugin[];
|
local: LocalPlugin[];
|
||||||
remote?: RemotePlugin[];
|
remote?: RemotePlugin[];
|
||||||
|
instance?: InstancePlugin[];
|
||||||
pluginErrors: PluginError[];
|
pluginErrors: PluginError[];
|
||||||
}) => {
|
}) => {
|
||||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
|
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
|
||||||
|
|
||||||
// Both local and remote plugins are loaded
|
// Both local and remote plugins are loaded
|
||||||
if (local && remote) {
|
if (local && remote) {
|
||||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, remote, pluginErrors)));
|
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors })));
|
||||||
|
|
||||||
// Only remote plugins are loaded (remote timed out)
|
// Only remote plugins are loaded (remote timed out)
|
||||||
} else if (local) {
|
} else if (local) {
|
||||||
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes(local, [], pluginErrors)));
|
thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, pluginErrors })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
|
|||||||
details?: CatalogPluginDetails;
|
details?: CatalogPluginDetails;
|
||||||
error?: PluginErrorCode;
|
error?: PluginErrorCode;
|
||||||
angularDetected?: boolean;
|
angularDetected?: boolean;
|
||||||
|
// instance plugins may not be fully installed, which means a new instance
|
||||||
|
// running the plugin didn't started yet
|
||||||
|
isFullyInstalled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogPluginDetails {
|
export interface CatalogPluginDetails {
|
||||||
@@ -294,3 +297,7 @@ export type PluginVersion = {
|
|||||||
isCompatible: boolean;
|
isCompatible: boolean;
|
||||||
grafanaDependency: string | null;
|
grafanaDependency: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InstancePlugin = {
|
||||||
|
pluginSlug: string;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user