diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index f90d1c581b9..a86e32d6a1a 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -4,7 +4,15 @@ import { accessControlQueryParam } from 'app/core/utils/accessControl'; import { API_ROOT, GCOM_API_ROOT, INSTANCE_API_ROOT } from './constants'; import { isLocalPluginVisibleByConfig, isRemotePluginVisibleByConfig } from './helpers'; -import { LocalPlugin, RemotePlugin, CatalogPluginDetails, Version, PluginVersion, InstancePlugin } from './types'; +import { + LocalPlugin, + RemotePlugin, + CatalogPluginDetails, + Version, + PluginVersion, + InstancePlugin, + ProvisionedPlugin, +} from './types'; export async function getPluginDetails(id: string): Promise { const remote = await getRemotePlugin(id); @@ -125,6 +133,14 @@ export async function getInstancePlugins(): Promise { return instancePlugins; } +export async function getProvisionedPlugins(): Promise { + const { items: provisionedPlugins }: { items: Array<{ type: string }> } = await getBackendSrv().get( + `${INSTANCE_API_ROOT}/provisioned-plugins` + ); + + return provisionedPlugins.map((plugin) => ({ slug: plugin.type })); +} + export async function installPlugin(id: string) { // This will install the latest compatible version based on the logic // on the backend. diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index faef81ce920..27887a93d1d 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -112,6 +112,30 @@ describe('Plugins/Helpers', () => { config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall; config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; }); + + test('plugins should be fully installed if they are installed and it is provisioned', () => { + const pluginId = 'plugin-1'; + + const oldFeatureTogglesManagedPluginsInstall = config.featureToggles.managedPluginsInstall; + const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; + + config.featureToggles.managedPluginsInstall = true; + config.pluginAdminExternalManageEnabled = true; + + const merged = mergeLocalsAndRemotes({ + local: [...localPlugins, getLocalPluginMock({ id: pluginId })], + remote: [...remotePlugins, getRemotePluginMock({ slug: pluginId })], + provisioned: [{ slug: pluginId }], + }); + const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId); + + expect(merged).toHaveLength(5); + expect(findMerged(pluginId)).not.toBeUndefined(); + expect(findMerged(pluginId)?.isFullyInstalled).toBe(true); + + config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall; + config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; + }); }); describe('mergeLocalAndRemote()', () => { diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 950496c28ee..c373e440dc6 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -7,17 +7,27 @@ import { contextSrv } from 'app/core/core'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { AccessControlAction } from 'app/types'; -import { CatalogPlugin, InstancePlugin, LocalPlugin, RemotePlugin, RemotePluginStatus, Version } from './types'; +import { + CatalogPlugin, + InstancePlugin, + LocalPlugin, + ProvisionedPlugin, + RemotePlugin, + RemotePluginStatus, + Version, +} from './types'; export function mergeLocalsAndRemotes({ local = [], remote = [], instance = [], + provisioned = [], pluginErrors: errors, }: { local: LocalPlugin[]; remote?: RemotePlugin[]; instance?: InstancePlugin[]; + provisioned?: ProvisionedPlugin[]; pluginErrors?: PluginError[]; }): CatalogPlugin[] { const catalogPlugins: CatalogPlugin[] = []; @@ -28,6 +38,11 @@ export function mergeLocalsAndRemotes({ return map; }, new Map()); + const provisionedSet = provisioned.reduce((map, provisionedPlugin) => { + map.add(provisionedPlugin.slug); + return map; + }, new Set()); + // add locals local.forEach((localPlugin) => { const remoteCounterpart = remote.find((r) => r.slug === localPlugin.id); @@ -51,7 +66,7 @@ export function mergeLocalsAndRemotes({ if (configCore.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled) { catalogPlugin.isFullyInstalled = catalogPlugin.isCore ? true - : instancesMap.has(remotePlugin.slug) && catalogPlugin.isInstalled; + : (instancesMap.has(remotePlugin.slug) || provisionedSet.has(remotePlugin.slug)) && catalogPlugin.isInstalled; catalogPlugin.isInstalled = instancesMap.has(remotePlugin.slug) || catalogPlugin.isInstalled; diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index 0afc9560ebd..bff4b094b51 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -16,10 +16,11 @@ import { installPlugin, uninstallPlugin, getInstancePlugins, + getProvisionedPlugins, } from '../api'; import { STATE_PREFIX } from '../constants'; import { mapLocalToCatalog, mergeLocalsAndRemotes, updatePanels } from '../helpers'; -import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin } from '../types'; +import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin } from '../types'; // Fetches export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => { @@ -31,6 +32,10 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall ? from(getInstancePlugins()) : of(undefined); + const provisioned$ = + config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall + ? from(getProvisionedPlugins()) + : of(undefined); const TIMEOUT = 500; const pluginErrors$ = from(getPluginErrors()); const local$ = from(getLocalPlugins()); @@ -48,6 +53,7 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t local: local$, remote: remote$, instance: instance$, + provisioned: provisioned$, pluginErrors: pluginErrors$, }) .pipe( @@ -66,13 +72,21 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t if (remote.length > 0) { const local = await lastValueFrom(local$); const instance = await lastValueFrom(instance$); + const provisioned = await lastValueFrom(provisioned$); const pluginErrors = await lastValueFrom(pluginErrors$); - thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors }))); + thunkApi.dispatch( + addPlugins(mergeLocalsAndRemotes({ local, remote, instance, provisioned, pluginErrors })) + ); } }); - return forkJoin({ local: local$, instance: instance$, pluginErrors: pluginErrors$ }); + return forkJoin({ + local: local$, + instance: instance$, + provisioned: provisioned$, + pluginErrors: pluginErrors$, + }); }, }) ) @@ -81,18 +95,22 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t local, remote, instance, + provisioned, pluginErrors, }: { local: LocalPlugin[]; remote?: RemotePlugin[]; instance?: InstancePlugin[]; + provisioned?: ProvisionedPlugin[]; pluginErrors: PluginError[]; }) => { // Both local and remote plugins are loaded if (local && remote) { thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` }); thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/fulfilled` }); - thunkApi.dispatch(addPlugins(mergeLocalsAndRemotes({ local, remote, instance, pluginErrors }))); + thunkApi.dispatch( + addPlugins(mergeLocalsAndRemotes({ local, remote, instance, provisioned, pluginErrors })) + ); // Only remote plugins are loaded (remote timed out) } else if (local) { diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 7c54128e001..5cc0089fb96 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -322,3 +322,7 @@ export type InstancePlugin = { pluginSlug: string; version: string; }; + +export type ProvisionedPlugin = { + slug: string; +};