Plugins: Fetch instance provisioned plugins in cloud, to check full installation (#83784)

This commit is contained in:
Hugo Kiyodi Oshiro 2024-03-05 16:30:14 +01:00 committed by GitHub
parent 7b4925ea37
commit 7f970d4887
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 84 additions and 7 deletions

View File

@ -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<CatalogPluginDetails> {
const remote = await getRemotePlugin(id);
@ -125,6 +133,14 @@ export async function getInstancePlugins(): Promise<InstancePlugin[]> {
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) {
// This will install the latest compatible version based on the logic
// on the backend.

View File

@ -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()', () => {

View File

@ -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<string, InstancePlugin>());
const provisionedSet = provisioned.reduce((map, provisionedPlugin) => {
map.add(provisionedPlugin.slug);
return map;
}, new Set<string>());
// 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;

View File

@ -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) {

View File

@ -322,3 +322,7 @@ export type InstancePlugin = {
pluginSlug: string;
version: string;
};
export type ProvisionedPlugin = {
slug: string;
};