From c16c764c6fc71993bc3ba09bde770f66ca706b9b Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro Date: Tue, 23 Sep 2025 09:58:06 -0300 Subject: [PATCH] Plugins: Set isProvisioned for local plugins without remote counterpart (#111268) --- .../features/plugins/admin/helpers.test.ts | 23 ++++++ public/app/features/plugins/admin/helpers.ts | 82 +++++++++++-------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/public/app/features/plugins/admin/helpers.test.ts b/public/app/features/plugins/admin/helpers.test.ts index 63dfb58a7a8..31e6cbddc44 100644 --- a/public/app/features/plugins/admin/helpers.test.ts +++ b/public/app/features/plugins/admin/helpers.test.ts @@ -158,6 +158,29 @@ describe('Plugins/Helpers', () => { config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; }); + + test('local plugins without remote counterpart should also also have isProvisioned correctly added', () => { + const oldPluginAdminExternalManageEnabled = config.pluginAdminExternalManageEnabled; + + config.pluginAdminExternalManageEnabled = true; + + const merged = mergeLocalsAndRemotes({ + local: localPlugins, + remote: [], + provisioned: [{ slug: localPlugins[0].id }], + }); + const findMerged = (mergedId: string) => merged.find(({ id }) => id === mergedId); + + expect(merged).toHaveLength(localPlugins.length); + expect(findMerged(localPlugins[0].id)).not.toBeUndefined(); + expect(findMerged(localPlugins[0].id)?.isProvisioned).toBe(true); + expect(findMerged(localPlugins[1].id)).not.toBeUndefined(); + expect(findMerged(localPlugins[1].id)?.isProvisioned).toBe(false); + expect(findMerged(localPlugins[2].id)).not.toBeUndefined(); + expect(findMerged(localPlugins[2].id)?.isProvisioned).toBe(false); + + config.pluginAdminExternalManageEnabled = oldPluginAdminExternalManageEnabled; + }); }); describe('mergeLocalAndRemote()', () => { diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 171644b2546..10398e4430b 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -31,57 +31,45 @@ export function mergeLocalsAndRemotes({ const catalogPlugins: CatalogPlugin[] = []; const errorByPluginId = groupErrorsByPluginId(errors); - const instancesMap = instance.reduce((map, instancePlugin) => { - map.set(instancePlugin.pluginSlug, instancePlugin); - return map; - }, new Map()); - - const provisionedSet = provisioned.reduce((map, provisionedPlugin) => { - map.add(provisionedPlugin.slug); - return map; - }, new Set()); + const remoteSet = new Set(remote?.map((plugin) => plugin.slug)); + const localMap = new Map(local.map((plugin) => [plugin.id, plugin])); + const instancesMap = new Map(instance?.map((plugin) => [plugin.pluginSlug, plugin])); + const provisionedSet = new Set(provisioned?.map((plugin) => plugin.slug)); // add locals local.forEach((localPlugin) => { - const remoteCounterpart = remote.find((r) => r.slug === localPlugin.id); const error = errorByPluginId[localPlugin.id]; - if (!remoteCounterpart) { - catalogPlugins.push(mergeLocalAndRemote(localPlugin, undefined, error)); + if (!remoteSet.has(localPlugin.id)) { + let catalogPlugin = mergeLocalAndRemote(localPlugin, undefined, error); + if (config.pluginAdminExternalManageEnabled) { + catalogPlugin = mergeCloudState( + catalogPlugin, + instancesMap, + provisionedSet.has(localPlugin.id), + localMap.has(localPlugin.id) + ); + } + catalogPlugins.push(catalogPlugin); } }); // add remote remote.forEach((remotePlugin) => { - const localCounterpart = local.find((l) => l.id === remotePlugin.slug); + const localCounterpart = localMap.get(remotePlugin.slug); const error = errorByPluginId[remotePlugin.slug]; const shouldSkip = remotePlugin.status === RemotePluginStatus.Deprecated && !localCounterpart; // We are only listing deprecated plugins in case they are installed. if (!shouldSkip) { - const catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error); - - // for managed instances, check if plugin is installed, but not yet present in the current instance + let catalogPlugin = mergeLocalAndRemote(localCounterpart, remotePlugin, error); if (config.pluginAdminExternalManageEnabled) { - catalogPlugin.isFullyInstalled = catalogPlugin.isCore - ? true - : (instancesMap.has(remotePlugin.slug) || provisionedSet.has(remotePlugin.slug)) && catalogPlugin.isInstalled; - - catalogPlugin.isInstalled = instancesMap.has(remotePlugin.slug) || catalogPlugin.isInstalled; - - const instancePlugin = instancesMap.get(remotePlugin.slug); - catalogPlugin.isUpdatingFromInstance = - instancesMap.has(remotePlugin.slug) && - catalogPlugin.hasUpdate && - catalogPlugin.installedVersion !== instancePlugin?.version; - - if (instancePlugin?.version && instancePlugin?.version !== remotePlugin.version) { - catalogPlugin.hasUpdate = true; - } - - catalogPlugin.isUninstallingFromInstance = Boolean(localCounterpart) && !instancesMap.has(remotePlugin.slug); - catalogPlugin.isProvisioned = provisionedSet.has(remotePlugin.slug); + catalogPlugin = mergeCloudState( + catalogPlugin, + instancesMap, + provisionedSet.has(remotePlugin.slug), + localMap.has(remotePlugin.slug) + ); } - catalogPlugins.push(catalogPlugin); } }); @@ -476,3 +464,27 @@ export function isNonAngularVersion(version?: Version) { export function isDisabledAngularPlugin(plugin: CatalogPlugin) { return plugin.isDisabled && plugin.error === PluginErrorCode.angular; } + +export function mergeCloudState( + catalogPlugin: CatalogPlugin, + instanceMap: Map, + isProvisioned: boolean, + hasLocal: boolean +) { + const instancePlugin = instanceMap.get(catalogPlugin.id); + + return { + ...catalogPlugin, + isFullyInstalled: catalogPlugin.isCore + ? true + : (instanceMap.has(catalogPlugin.id) || isProvisioned) && catalogPlugin.isInstalled, + isInstalled: instanceMap.has(catalogPlugin.id) || catalogPlugin.isInstalled, + isUpdatingFromInstance: + instanceMap.has(catalogPlugin.id) && + catalogPlugin.hasUpdate && + catalogPlugin.installedVersion !== instancePlugin?.version, + hasUpdate: Boolean(instancePlugin?.version && instancePlugin?.version !== catalogPlugin.latestVersion), + isUninstallingFromInstance: hasLocal && !instanceMap.has(catalogPlugin.id), + isProvisioned: isProvisioned, + }; +}