-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plugin Widgets and Composability #1536
Comments
A few initial thoughts on this topic: Display Rules - I'm assuming the RFC describes a Tabs/Routes - Can widgets be included into tabs within a plugin? For instance, the catalog entity page might have tabs that contain widgets that are specific to the org/domain/entity. Perhaps the tab itself would be a customizable widget that just routes somewhere with some params. Or, maybe these would just be widgets on the same route. Input Data - Many widgets will likely require some sort of input values to render the appropriate data within the context they exist. For instance, within the catalog entity page, it would need to know the entity being displayed. This would likely require some formal metadata that both the widget and widget host use to indicate what to render. Host Containers - Do widget consumers expose slots where certain widgets can be inserted? If so, then perhaps there also needs to be metadata for what types of widgets a container can support. Configuration - How are widgets and containers configured? Do they work auto magically via some conventions, or do they require full configuration. Ideally, there are smart defaults for widgets to avoid configuration in many cases. Perhaps some widgets can suggest being injected into well known containers (e.g. entity page) if the plugin itself is registered. Some widgets may be opt-in using a simple toggle. Others might require more complex configuration. |
An alternative to a registry is just regular exports from the plugin package. Regarding layout I do feel we need some solution for setting constraints for how a plugin can be displayed and probably a method for choosing how to display them within those constraints.
I'm thinking that we strive for an API that is flexible enough to do more than just info boxes, so also tabs and for example header items.
Indeed, some early experiments in this repo used a React context for the entity information. Internally we pass props to widgets, which are embedded in the type of a "widget space".
Internally we have the concept of "widget spaces", that are owned by plugins. Plugins register their widgets to different widget spaces at startup, along with the configuration that is required to add a widget to that space. The plugins can pick widget from the space and choose how to render them themselves. The widgets spaces also have user settings associated with them that allows customization of the widgets by the user. It's a pretty nice and flexible way to build something like a "widget store" where users can select widget that they want to display themselves, but as a method for composition it doesn't really have everything that I think we need. You end up lacking a lot of control of where things end up being rendered, as it kinda just depends on the order plugins are being loaded or the user. It's also tricky to be able to decide other things through the app, like wanting to hide widgets on some pages. Another issue is that it also doesn't compose well, in case you want to do something like creating a widget that uses widgets from other plugins. I do think a web components-like slots concept might be the way to go, and gonna dive into that a bit more. I'm also thinking that the API would be something for the app to use to compose everything, with the possibility of building higher-level APIs on top.
I think we'll see about this, but most likely I imagine it'll work a lot like react props. |
Scope:This RFC looks into the problem of composing different plugins together in Backstage, allowing them to extend each others functionality. Definitions:Extension - piece of functionality that integrates into other plugin. Can be Tab, InfoCard, Route or something else. Host plugin - plugin that hosts other plugins extensions. Widget - prepared component, exported from plugin that is intended to be used as an extension. Extension point - defined by the plugin that allows others to "extend" it, e.g. adding additional tab or a card. Defines a contract on how this extension is going to be instantiated. Actors:Let’s define different type of users for our Backstage instance (APP below)
MotivationsPlugin developer:I want to create a piece of software that will help the users. I envision several use cases, e.g. going into a separate route, having a simple card in the entity overview, having more complex view under a tab in the entity view, maybe having some information available for share with other plugins? I also respect other plugin developers and admins, so I want to offer nice experience on setting my plugin up. Another thing - I'm looking in the future and want to be able to allow other devs to hook up into my plugin, therefore I want leave some extension points that people can use to do so. I also want to define those in a visible manner - typed and with easy-to-follow (i.e. standard) way of using them. APP admin:I want the installation process to be easy and straightforward. I need to be able to select which parts of the plugins are installed in APP. I need to able to configure plugins before deployment and adjust they behaviour runtime (e.g. based on a type of entity). APP user:I want to have the functionality that plugin promises to bring to be available in the most convenient way - information should be coupled in a sensible manner, e.g. in the entity view I want to see the overview of a plugin-provided data. Also, as an owner of projects A and B, I want to be able to configure parts of the plugins differently for each project (I don't care about some metrics for project A, but very interested in those for project B). Proposed architecture:
Key moments:
Conclusion:This approach brings a lot of composability to the table, but with great power comes great responsibility😜. It may be that we want to do more in terms of safeguards and general rules. Currently, the weakest part is our routing system. Nested routes, figuring out which routes are active while you are in several nested routes, support for breadcrumbs, routes that needs to be changed based on the placement (example: github-actions plugin widget has the link to the Also, the runtime configuration system for the plugins is not there yet, albeit there are some discussions happening. |
@shmidt-i Great write-up and awesome to bring in some good terminology around the concepts, way better than what we have internally 😅 Could of thoughts/questions before we dive deeper: In 1 you mention that the registry should be reactive, is there something in the design that makes that a requirement? Internally it's all just synchronously wired up before the app renders. The conditions in 3 may be better to just leave to the Extension component itself, i.e. just return 4 is super tricky and I'm not sure we can have a plugin or app-driven registration tbh. I'd like to explore a JSX-based API for the APP as the lowest-level composition API, on top of which we could possibly build some higher-level (config driven?) methods of composing plugins. It seems to me like you'll always end up with issues of order and separate configuration for different plugins unless you have very fine-grained registration of Extensions, at which point you're essentially building up a component tree using a flat JS API and you'd be better off with JSX. Regarding the routing issues I'm hoping we can extend the |
Not sure if it needs repeating, but as you know I'm |
So we had a discussion about this and came up with 2 things:
<CatalogPageTemplate type="service">
<MyHeaderSlotConsumer slot="header" />
<Tabs slot="content">
<Tab title="Overview">
<WidgetView>
<WorkflowWidget />
</WidgetView>
</Tab>
<Tab title="CI/CD">
<WorkflowRunsTable/>
<WorkflowRunDetails/>
</Tab>
</Tabs>
<PageFooter slot="footer"/>
</CatalogPageTemplate>
const CatalogPageTemplate = () => {
return (
<Page>
<Slot name="header">
<Header/>
</Slot>
<Content>
<Slot name="content"/>
</Content>
<Slot name="footer"/>
</Page>
)
} And the next step being looking more into the JSX-based approach |
Another take is that we don't need a reactive registry from the beginning, it can be just a synchronous registering on the app startup and then passing that down through e.g. React.Context |
Some output from another brainstorming session today. We came up with a couple of ideas/hypothesises that we want to try out in a more concrete implementation:
Let's keep filling in the above list as we progress. Some other output was more high-level sample code from the app's point of view. We primarily focused on finding a good level of information and structure that would make it easy for anyone coming from the website to navigate the code. Particularly focusing on the use-case of finding for example the cards on a particular overview page, and then rearranging or reconfiguring them. const App = (
<>
<CatalogPage>
<ComponentList />
</CatalogPage>
{/* pages/components/service.tsx */}
<EntityContext filters={[kind('Component'), type('service')]}>
<TabbedPage>
<CardPage>
<ReadmeCard />
<GitHubWorkflowsCard />
<MetadataCard />
</CardPage>
<SpotifyCiCdPage />
</TabbedPage>
</EntityContext>
{/* pages/components/website.tsx */}
<EntityContext filters={[kind('Component'), type('website')]}>
<TabbedPage>
<TabbedPage.Tab title="Overview">
<CardPage>
<ReadmeCard size={2} />
<LighthouseCard size={4} />
<GitHubWorkflowsCard />
<MetadataCard />
</CardPage>
</TabbedPage.Tab>
<TabbedPage.Tab title="CI/CD">
<SpotifyCiCdPage />
</TabbedPage.Tab>
</TabbedPage>
</EntityContext>
<ScaffolderPage>
<ScaffolderTemplates />
</ScaffolderPage>
<GraphiQLPage>
<GitHubEndpoint />
<GitLabEndpoint />
<BackstageEndpoint />
</GraphiQLPage>
</>
); Another idea was switches for selecting content, where the first matching page for a given entity will be displayed: const SpotifyCiCdPage = () => (
<Switch>
<GithubWorkflowPage>
<GithubWorkflowListPage />
<GithubWorkflowDetailsPage />
</GithubWorkflowPage>
<CircleCiWorkflowPage>
<CircleCiWorkflowListPage />
<CircleCiWorkflowDetailsPage />
</CircleCiWorkflowPage>
</Switch>
); |
Quick update that this is still something we're looking at, specifically how to solve routing between different plugins, and different components exported from a plugin. Some more info here: #2565 |
Core team has been focusing on this along with a complete implementation of routing and Going at it through a bunch of different experiments, all collected in this branch around here:
Experiment 11 is a particular one where we started getting more happy with the implementation and simplicity. We're essentially allowing some custom data to be tied to individual react elements, and then use that for route and plugin discovery. Every component (and maybe other things too) that a plugin exports is wrapped up and exported as an "Extension", which is a point were we'll be able to let the core APIs wrap plugin components with additional functionality and context. It is also somethings that can be used for composability, as the API would be open for plugins to export their own This is still experiments though, no naming is settled, and the widget stuff you see in experiment 11 is not something we're planning to add now either 😅 Please reach out here or on Discord if this triggered any questions or ideas, or if you want to get involved in general! |
The architecture proposed here is very exciting and looks great from TechDocs point of view (from me and @hooloovooo). Examples of some widgets that TechDocs will most certainly use/create in coming future -
(A lot more ideas will come in future.) We will start implementing some of these widgets for TechDocs in 2021 Q1. If the Extension APIs, registry, etc. are available to use, we will make use of it. But even if they are not available timeline-wise, that is probably okay. We can always expose the widgets later on. Looking forward to the great work on this. :) 🚀 🤞 |
Feature Suggestion
Plugins should be able to provide not only entire pages, but also smaller parts of a page, such as a card on an overview page, or an item in a header. This has always been desirable as it would allow plugins to be composed of components from other plugins, but with the addition of the service catalog and overview pages, the need has become even more clear and urgent.
Possible Implementation
We likely want to go beyond simply importing React components across plugins, as we at least want components to be wrapped in a context for plugin, and possibly provide some constraints for how and where components can be used. We also want to provide an API where the developers of each individual Backstage app can decide what plugin components go where. A very high level example of this is given in chapter 4 of #1233, but it is in no way decided that we want to go with that API.
Context
We've been looking at this issue for a while as a part of introducing the service catalog. The purpose of this issue is to have a more open place for discussion and for the community to be able to provide feedback and ideas.
The text was updated successfully, but these errors were encountered: