Skip to content

[BREAKINGCHANGE] fetch plugin based on version#128

Open
shahrokni wants to merge 1 commit into
mainfrom
feat/plugin_versioning_front
Open

[BREAKINGCHANGE] fetch plugin based on version#128
shahrokni wants to merge 1 commit into
mainfrom
feat/plugin_versioning_front

Conversation

@shahrokni
Copy link
Copy Markdown
Contributor

@shahrokni shahrokni commented May 4, 2026

perses/perses#1186

⚠️ BACKEND NEEDS TO BE UPDAED. EXPLAINED AT THE END OF DESCRIPTION

Different Plugins Versions and Registries

So far a plugin could be imported by its name only.

const remoteEntryURL = baseURL ? `${baseURL}/${name}/mf-manifest.json` : `/plugins/${name}/mf-manifest.json`;

The backend has recently introduced a feature by which multiple versions of the same plugin could be available.
This means if the consumer of the Plugin Registry provided the version and registry, the exact desired plugin could be loaded in the front!

To achieve the mentioned goal the new URL will carry the version and registry as well. If not provided by the consumer, the front uses the default values introduced by the backend which are latest and perses.dev

 const remoteEntryURL = baseURL
      ? `${baseURL}/${name}/${registry || DEFAULT_PLUGIN_REGISTRY}/${version || DEFAULT_PLUGIN_VERSION}/mf-manifest.json`
      : `/plugins/${name}/${registry || DEFAULT_PLUGIN_REGISTRY}/${version || DEFAULT_PLUGIN_VERSION}/mf-manifest.json`;

/*ESAMPLE FROM MY LOCAL MACHINE BY DEFAULT VALUES*/
/* http://localhost:3000/plugins/TimeSeriesChart/perses.dev/latest/mf-manifest.json */

⚠️ Backend needs to do

Backend should be adjusted to manage the new URL. I checked the backend and understood that at the moment the intercepted GET request is translated to the address of the files on the disk. Please take a look at (ui\endpoint.go)
I believe the following shared code should be adjusted for the new URL that I just explained.

if devEnvironment == nil {
		// In that case, we need to read the requested files from the file system.
		// The First thing to do is to replace the URL path with the local path of the plugin.
		localPath := strings.Replace(req.URL.Path, fmt.Sprintf("%s/plugins/%s", f.apiPrefix, pluginName), loaded.LocalPath, 1)

		// Then we just need to rely on the echo router to serve the file.

		// X-Content-Type-Options: nosniff is an HTTP response header that tells browsers: "Do not try to guess the content type — trust the Content-Type header I sent you."
		// Without it, browsers perform "MIME sniffing". For example, a file served as text/plain could be sniffed as text/html and executed as HTML, which could open the door to XSS attacks.
		// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options
		c.Response().Header().Set("X-Content-Type-Options", "nosniff")
		return c.File(localPath)
	}

There is also a TODO comment in backend that I believe could to be addressed now

// TODO: These hardcoded values should be replaced once the frontend is able to manage multiple registries and plugin versions.
	// This suppose to update the plugin system first.
	// These hardocded values are matching the default behavior of the plugin system and it helps to keep backward compatibility.
	loaded, isLoaded := f.pluginService.GetLoadedPlugin(pluginName, pluginModel.LatestVersion, pluginModel.DefaultRegistry)
	if !isLoaded || !loaded.Module.Status.IsLoaded {
		logrus.Errorf("unable to find the plugin => %s", pluginName)
		return apiinterface.NotFoundError
	}

Checklist

  • Pull request has a descriptive title and context useful to a reviewer.
  • Pull request title follows the [<catalog_entry>] <commit message> naming convention using one of the
    following catalog_entry values: FEATURE, ENHANCEMENT, BUGFIX, BREAKINGCHANGE, DOC,IGNORE.
  • All commits have DCO signoffs.

UI Changes

  • Changes that impact the UI include screenshots and/or screencasts of the relevant changes.
  • Code follows the UI guidelines.
  • E2E tests are stable and unlikely to be flaky.
    See e2e docs for more details. Common issues include:
    • Is the data inconsistent? You need to mock API requests.
    • Does the time change? You need to use consistent time values or mock time utilities.
    • Does it have loading states? You need to wait for loading to complete.

@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch 10 times, most recently from 4d0516d to d604ac7 Compare May 6, 2026 11:03
@shahrokni shahrokni marked this pull request as ready for review May 6, 2026 12:31
@shahrokni shahrokni requested a review from a team as a code owner May 6, 2026 12:31
@shahrokni shahrokni requested review from Nexucis and jgbernalp May 6, 2026 12:32
Comment thread plugin-system/src/test-utils/mock-plugin-registry.tsx Outdated
Comment thread plugin-system/src/remote/remotePluginLoader.ts Outdated
Comment thread plugin-system/src/components/PluginRegistry/plugin-indexes.ts Outdated
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
Comment thread plugin-system/src/remote/remotePluginLoader.test.ts Outdated
@Nexucis
Copy link
Copy Markdown
Member

Nexucis commented May 6, 2026

Thank you @shahrokni for updating the frontend regarding the plugin multi version management !

Indeed the backend will need to be updated at some point, but hopefully that should be as easy as to update the regexp

capturingPluginName = regexp.MustCompile(`/plugins/([a-zA-Z0-9_-]+)/?.*`)

to catch the registry and the version.

Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/model/plugins.ts Outdated
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from 192e537 to e9f8773 Compare May 7, 2026 15:26
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from 582eb82 to 48908cf Compare May 8, 2026 13:19
@shahrokni shahrokni marked this pull request as ready for review May 8, 2026 13:20
@shahrokni shahrokni requested a review from jgbernalp May 8, 2026 13:20
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from 48908cf to a179a07 Compare May 8, 2026 13:37
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch 3 times, most recently from 58660be to 4dedf66 Compare May 11, 2026 09:01
@shahrokni shahrokni changed the title [FEATURE] fetch plugin based on version [BREAKINGCHANE] fetch plugin based on version May 11, 2026
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from 4dedf66 to 52b534a Compare May 11, 2026 09:46
@shahrokni shahrokni requested a review from jgbernalp May 11, 2026 09:52
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/remote/PluginRuntime.tsx Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
@jgbernalp jgbernalp changed the title [BREAKINGCHANE] fetch plugin based on version [BREAKINGCHANGE] fetch plugin based on version May 11, 2026
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
Comment thread plugin-system/src/remote/remotePluginLoader.test.ts Outdated
Comment thread plugin-system/src/components/PluginRegistry/PluginRegistry.tsx Outdated
@shahrokni shahrokni marked this pull request as draft May 11, 2026 13:19
@shahrokni shahrokni marked this pull request as ready for review May 11, 2026 13:42
@shahrokni shahrokni requested a review from jgbernalp May 11, 2026 13:42
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from fb57b58 to 0d2ec31 Compare May 12, 2026 12:38
Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>

Signed-off-by: Seyed Mahmoud SHAHROKNI <seyedmahmoud.shahrokni@amadeus.com>
@shahrokni shahrokni force-pushed the feat/plugin_versioning_front branch from fbee7e9 to c291fbc Compare May 12, 2026 12:56
* Note: It is likely that the key is not found. However, the search should NOT give up easily!
* Instead it should continue with the fallback mechanism
*/
compoundKey = `${kind}:${name}:${registry}:${version}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should reuse the getPluginModuleCompoundKey otherwise some strings with undefined will be generated.

pluginIndexesCache.current = request;

// Remove failed requests from the cache so they can potentially be retried
request.catch(() => pluginIndexesCache.current === undefined);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous issue, but should this be?

Suggested change
request.catch(() => pluginIndexesCache.current = undefined);

},
importPlugin,
} = p;
importMap.set(`${kind}:${name}:${registry ?? ''}:${version ?? ''}`, { resource, importPlugin });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here: getPluginModuleCompoundKey

kind,
metadata: { name, version, registry },
} = resource;
const { importPlugin } = importMap.get(`${kind}:${name}:${registry ?? ''}:${version ?? ''}`) || {};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPluginModuleCompoundKey

resource = pluginIndexes.pluginResourcesByNameKindRegistryVersion.get(compoundKey);
if (resource) {
pluginModule = (await loadPluginModule(resource)) as Record<string, Plugin<UnknownSpec>>;
const plugin = pluginModule?.[`${name}:${registry ?? ''}:${version ?? ''}`];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should also have a function for this name, it might seem trivial but is repeated a couple of times.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its the compound key. We can use the same function no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants