[RFC] Make existing components configurable
See original GitHub issueStatus: Open for comments
Need
As a platform developer by using backstage as a foundation I would like to have an instrument to modify existing components with ease:
replacing a nested component modifying the parts of components through configuration, like for example:
- adding additional labels/texts
- providing custom icons
- making buttons/icons/labels/links enabled/disabled based on custom logic.
Proposal
The proposal is to use a metadata configuration to loading and configuring the components.
The more straightforward and mostly used in React application is to provide it via properties.
As the input of configuration is going to be defined on the top level, then the properties could be provided in a hierarchical way.
For example:
{
plugins: {
component: {
name: 'AboutCard',
disableIconButton: (context: Context) => context.entity.metadata.annotations?.['backstage.io/edit-url'] && context.permissions.hasRole('ADMIN')
}
}
}
which will be supplied with an appropriate schema. Like for the above-mentioned example:
type Context {
entity: Entity;
permissions: Permissions
}
type Properties = {
plugins: {
component: {
name: string;
disableIconButton: (context: Context) => boolean
}
}
}
The disadvantage could be with the hassle of propagating the tree of properties across all components.
Alternatives
Other way could be the alternative way to define metadata/configuration via external configuration files like yaml/json.
Alternative #1
Same way as now app-config.yaml is used for more high-level configuration.
- plugins:
- name: catalog
component:
- name: AboutCard
disableIconButton: /path/to/file/with/defined/disableIconButtonFunction.ts
title: /path/to/file/with/defined/renderTitleFunction.ts
Where the file contains only 1 function and get a context, based on which the logic of enabling/disabling could be decided.
For example:
type Context {
entity: Entity;
permissions: Permissions
}
export default function (context: Context) {
return context.entity.metadata.annotations?.['backstage.io/edit-url'] && context.permissions.hasRole('ADMIN');
}
Same way could be done the replacement of the components.
If for example I would like to replace AboutCard to MyAboutCard cause I want to see it absolutely differently then now, I can do like:
- plugins:
- name: catalog
component:
- name: AboutCard
location: /path/to/file/with/defined/component.ts
The concept one of the ways how to make dynamic loading of components is here: https://javascript.plainenglish.io/how-to-load-a-dynamic-script-in-react-2940d30998dd
For this format also can be used type definitions supplied in typescript and validated during runtime with https://github.com/gcanti/io-ts/
And on top of that to generate API documentation with tools like http://typedoc.org/
Alternative #2
It could be also the approach in the middle of the initial proposition and alternative #1.
By supplying as a configuration not a file, but typescript object, which will be consumed with the underlying components not via properties but via Redux store (or similar).
That way each component “knows” which section of redux store to read.
This is quite similar how in backstage now works useEntity( ) and other similar methods.
Risks
The codebase will have an extra abstract layer which has to be propagated to all configurable embedded components and plugins.
Keeping straightforward implementation which can’t be extended/modified is more simple to develop and maintain.
But such approach has limitations when it is used by other vendors which would like to do certain things in an own way.
UX decisions, look and feel can have different standards from company to company.
Current approach of copy pasting the code, making a fork of certain part of backstage won’t play well in a long run, as it entails the high maintenance costs especially after each major backstage release.
P.S.
This is a quite crucial feature for us in bol.com. In case of getting to a mutual conclusion we are open to volunteer our help to implement the proposal.
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:17 (16 by maintainers)
Top GitHub Comments
I’m actually leaning towards an approach where these configurations aren’t done at the app level, but rather as some form of plugin decoration managed as a plugin itself. This is from the point of view of hundreds of plugins in a large installation, each with their own separate owners from various parts of an organization. There’s a mix of both internal plugins, as well as external plugins, and the integration of these external plugins is not necessarily owned by the app integration team. If we put all of this configuration in the app I feel there is a large risk of it over time becoming mom’s spaghetti, with ownership all over the place.
Roughly the idea would be that a plugin would provide an optional API that essentially recreates itself with a new configuration. A rough idea:
The
acmeCatalogPlugin
would essentially be a reconfigured clone, and extensions would follow along (somehow).This ofc ends up playing very well with TypeScript and an open set of plugins, but it does perhaps add a bit more overhead.
I agree with you @Rugvip,
reconfigure
shouldn’t allow to add extra fields, but only override default.I incorporated feedback given @grantila to keep the configuration in registry with an ability to look it up with help of React context.
I kept the naming of it as
metadata
, notprovider
, asprovider
in backstage codebase is appended to Api, Routing, Themes, etc.PR: https://github.com/backstage/backstage/pull/11404