mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Fetch instance provisioned plugins in cloud, to check full installation (#83784)
This commit is contained in:
committed by
GitHub
parent
7b4925ea37
commit
7f970d4887
@@ -4,7 +4,15 @@ import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
|||||||
|
|
||||||
import { API_ROOT, GCOM_API_ROOT, INSTANCE_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, InstancePlugin } from './types';
|
import {
|
||||||
|
LocalPlugin,
|
||||||
|
RemotePlugin,
|
||||||
|
CatalogPluginDetails,
|
||||||
|
Version,
|
||||||
|
PluginVersion,
|
||||||
|
InstancePlugin,
|
||||||
|
ProvisionedPlugin,
|
||||||
|
} 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);
|
||||||
@@ -125,6 +133,14 @@ export async function getInstancePlugins(): Promise<InstancePlugin[]> {
|
|||||||
return instancePlugins;
|
return instancePlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getProvisionedPlugins(): Promise<ProvisionedPlugin[]> {
|
||||||
|
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) {
|
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.
|
||||||
|
|||||||
@@ -112,6 +112,30 @@ describe('Plugins/Helpers', () => {
|
|||||||
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
|
config.featureToggles.managedPluginsInstall = oldFeatureTogglesManagedPluginsInstall;
|
||||||
config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled;
|
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()', () => {
|
describe('mergeLocalAndRemote()', () => {
|
||||||
|
|||||||
@@ -7,17 +7,27 @@ 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, InstancePlugin, LocalPlugin, RemotePlugin, RemotePluginStatus, Version } from './types';
|
import {
|
||||||
|
CatalogPlugin,
|
||||||
|
InstancePlugin,
|
||||||
|
LocalPlugin,
|
||||||
|
ProvisionedPlugin,
|
||||||
|
RemotePlugin,
|
||||||
|
RemotePluginStatus,
|
||||||
|
Version,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export function mergeLocalsAndRemotes({
|
export function mergeLocalsAndRemotes({
|
||||||
local = [],
|
local = [],
|
||||||
remote = [],
|
remote = [],
|
||||||
instance = [],
|
instance = [],
|
||||||
|
provisioned = [],
|
||||||
pluginErrors: errors,
|
pluginErrors: errors,
|
||||||
}: {
|
}: {
|
||||||
local: LocalPlugin[];
|
local: LocalPlugin[];
|
||||||
remote?: RemotePlugin[];
|
remote?: RemotePlugin[];
|
||||||
instance?: InstancePlugin[];
|
instance?: InstancePlugin[];
|
||||||
|
provisioned?: ProvisionedPlugin[];
|
||||||
pluginErrors?: PluginError[];
|
pluginErrors?: PluginError[];
|
||||||
}): CatalogPlugin[] {
|
}): CatalogPlugin[] {
|
||||||
const catalogPlugins: CatalogPlugin[] = [];
|
const catalogPlugins: CatalogPlugin[] = [];
|
||||||
@@ -28,6 +38,11 @@ export function mergeLocalsAndRemotes({
|
|||||||
return map;
|
return map;
|
||||||
}, new Map<string, InstancePlugin>());
|
}, new Map<string, InstancePlugin>());
|
||||||
|
|
||||||
|
const provisionedSet = provisioned.reduce((map, provisionedPlugin) => {
|
||||||
|
map.add(provisionedPlugin.slug);
|
||||||
|
return map;
|
||||||
|
}, 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);
|
||||||
@@ -51,7 +66,7 @@ export function mergeLocalsAndRemotes({
|
|||||||
if (configCore.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled) {
|
if (configCore.featureToggles.managedPluginsInstall && config.pluginAdminExternalManageEnabled) {
|
||||||
catalogPlugin.isFullyInstalled = catalogPlugin.isCore
|
catalogPlugin.isFullyInstalled = catalogPlugin.isCore
|
||||||
? true
|
? 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;
|
catalogPlugin.isInstalled = instancesMap.has(remotePlugin.slug) || catalogPlugin.isInstalled;
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import {
|
|||||||
installPlugin,
|
installPlugin,
|
||||||
uninstallPlugin,
|
uninstallPlugin,
|
||||||
getInstancePlugins,
|
getInstancePlugins,
|
||||||
|
getProvisionedPlugins,
|
||||||
} 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, InstancePlugin } from '../types';
|
import { CatalogPlugin, RemotePlugin, LocalPlugin, InstancePlugin, ProvisionedPlugin } from '../types';
|
||||||
|
|
||||||
// Fetches
|
// Fetches
|
||||||
export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, thunkApi) => {
|
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
|
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
|
||||||
? from(getInstancePlugins())
|
? from(getInstancePlugins())
|
||||||
: of(undefined);
|
: of(undefined);
|
||||||
|
const provisioned$ =
|
||||||
|
config.pluginAdminExternalManageEnabled && configCore.featureToggles.managedPluginsInstall
|
||||||
|
? from(getProvisionedPlugins())
|
||||||
|
: of(undefined);
|
||||||
const TIMEOUT = 500;
|
const TIMEOUT = 500;
|
||||||
const pluginErrors$ = from(getPluginErrors());
|
const pluginErrors$ = from(getPluginErrors());
|
||||||
const local$ = from(getLocalPlugins());
|
const local$ = from(getLocalPlugins());
|
||||||
@@ -48,6 +53,7 @@ export const fetchAll = createAsyncThunk(`${STATE_PREFIX}/fetchAll`, async (_, t
|
|||||||
local: local$,
|
local: local$,
|
||||||
remote: remote$,
|
remote: remote$,
|
||||||
instance: instance$,
|
instance: instance$,
|
||||||
|
provisioned: provisioned$,
|
||||||
pluginErrors: pluginErrors$,
|
pluginErrors: pluginErrors$,
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -66,13 +72,21 @@ 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 instance = await lastValueFrom(instance$);
|
||||||
|
const provisioned = await lastValueFrom(provisioned$);
|
||||||
const pluginErrors = await lastValueFrom(pluginErrors$);
|
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,
|
local,
|
||||||
remote,
|
remote,
|
||||||
instance,
|
instance,
|
||||||
|
provisioned,
|
||||||
pluginErrors,
|
pluginErrors,
|
||||||
}: {
|
}: {
|
||||||
local: LocalPlugin[];
|
local: LocalPlugin[];
|
||||||
remote?: RemotePlugin[];
|
remote?: RemotePlugin[];
|
||||||
instance?: InstancePlugin[];
|
instance?: InstancePlugin[];
|
||||||
|
provisioned?: ProvisionedPlugin[];
|
||||||
pluginErrors: PluginError[];
|
pluginErrors: PluginError[];
|
||||||
}) => {
|
}) => {
|
||||||
// Both local and remote plugins are loaded
|
// Both local and remote plugins are loaded
|
||||||
if (local && remote) {
|
if (local && remote) {
|
||||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
|
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchLocal/fulfilled` });
|
||||||
thunkApi.dispatch({ type: `${STATE_PREFIX}/fetchRemote/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)
|
// Only remote plugins are loaded (remote timed out)
|
||||||
} else if (local) {
|
} else if (local) {
|
||||||
|
|||||||
@@ -322,3 +322,7 @@ export type InstancePlugin = {
|
|||||||
pluginSlug: string;
|
pluginSlug: string;
|
||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ProvisionedPlugin = {
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user