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
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 { 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.

View File

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

View File

@@ -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;

View File

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

View File

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