Skip to content
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

Support to register scaffolderPlugin.provide(createScaffolderFieldExtension( for dynamic plugin #1146

Closed
cmoulliard opened this issue Mar 28, 2024 · 15 comments · Fixed by #1253
Labels
jira Sync this issue with Jira kind/feature status/triage

Comments

@cmoulliard
Copy link

Support to register scaffolderPlugin.provide

It is not possible today to register using the dynamic configuration, a plugin packaging ScaffolderFieldExtension.
Such fields which are React field components are used part of a backstage template and are registered as such

// https://github.com/q-shift/backstage-plugins/blob/main/plugins/quarkus/src/plugin.ts#L7-L11

import {scaffolderPlugin} from '@backstage/plugin-scaffolder';
import {createScaffolderFieldExtension, FieldExtensionComponent} from '@backstage/plugin-scaffolder-react';
import {QuarkusExtensionList} from './scaffolder/QuarkusExtensionList';

export const QuarkusExtensionListField: FieldExtensionComponent<string, string> = scaffolderPlugin.provide(
    createScaffolderFieldExtension({
        name: 'QuarkusExtensionList',
        component: QuarkusExtensionList,
    }),
@github-actions github-actions bot added the jira Sync this issue with Jira label Mar 28, 2024
@davidfestal
Copy link
Contributor

cc @tumido

@cmoulliard
Copy link
Author

@tumido Have you been able to have a look ? Can I help you ?

@tumido
Copy link
Member

tumido commented Apr 3, 2024

Yes, this is currently not possible. I'm not sure if we want to evolve and enhance the current dynamic frontend system while there's parallel work ongoing in the upstream.

  1. We would need to add support for collecting, parsing and loading these as dynamic extensions, that can be easily done on the chrome/core app, similarly to what we do for dynamic pages etc.
  2. We would need to take a look into how the Scaffolder frontend is being rendered. I haven't looked into that at all, so it may even require us forking the Scaffolder frontend. That would totally change the scope of the work here.

@davidfestal
Copy link
Contributor

I'm not sure if we want to evolve and enhance the current dynamic frontend system while there's parallel work ongoing in the upstream.

I don't think it makes sense to add more features in the current dynamic frontend system which will risk making it more difficult to converge with the parallel upstream work.
If it doesn't prevent the core feature of the Quarkus plugin to work, then let's postpone the scaffolder field extension for later.

@davidfestal
Copy link
Contributor

davidfestal commented Apr 3, 2024

@christophe-f what do you think ?

@durandom
Copy link
Member

durandom commented Apr 3, 2024

  1. is there a hacky way to get the FieldExtension into the showcase app? Like hardcoding it or wrapping it in a feature flag, which could be enable by a check if the quarkus plugin is enabled. Pretty much like what you did with the Quarkus Tab
  2. how many of those quarkus extensions would be listed and are they always installation dependent? I.e. could the interim solution be a static dropdown that's populated in the template?

@cmoulliard ^^

@cmoulliard
Copy link
Author

2. how many of those quarkus extensions would be listed and are they always installation dependent?

Until now we have developed 3 fields and soon we will have 4. They are needed when a user scaffold a project using a template including them

@cmoulliard
Copy link
Author

2. I.e. could the interim solution be a static dropdown that's populated in the template?

This is doable but as the list of the quarkus versions change when new quarkus are out (like also the extensions liust), then it will be needed to find a way to populate the list using values passed as app-config.yaml field/parameter (aka similar to what users do with their git servers, etc)

@cmoulliard
Copy link
Author

There is something that I dont understand. Even if this is not well documented, backstage-next offers a way to configure some additional fields as you can see here: https://github.com/backstage/backstage/blob/master/packages/app-next/app-config.yaml#L149-L156

  - scaffolder.page:
       config:
         groups:
         - title: Recommended
           filter:
             entity.metadata.tags: recommended
   - scaffolder.page/fields: '@backstage/plugin-scaffolder#LowerCaseValuePickerFieldExtension'
   - scaffolder.page/layout: '@backstage/plugin-scaffolder#TwoColumnLayout'

Question: Does it work or not ? Is it something that we could use with backstage-showcase ?

@davidfestal
Copy link
Contributor

davidfestal commented Apr 3, 2024

Surely, that's the plan, but this backstage-next things were not available when the dynamic frontend plugins were introduced into the Janus showcase, which explains why we had to start with something Janus-specific which covered the main needs on the Janus side.

The plan is now to:

  • Contribute (with some adaptations) the dynamic frontend plugin system that has been done on Janus side to upstream, and integrate it with this new upstream frontend system
  • Switcg the Janus implementation to use this new upstream dynamic frontend plugin system.

That's precisely why we want to avoid, as much as possible, investing more in the Janus-specific implementation.

@cmoulliard
Copy link
Author

  • dynamic frontend plugin system

Is there a great example well documented explaining how to create a new front plugin, build it and configure it ?

@christophe-f
Copy link
Contributor

Is there a great example well documented explaining how to create a new front plugin, build it and configure it ?

It's currently in progress.

@christophe-f
Copy link
Contributor

This is definitely something that we need to provide. Platform engineers will need to add custom FieldExtension.

@cmoulliard
Copy link
Author

I started to experiment/play with new frontend using app-next package of backstage = 1.25 where I included the scaffolderPlugin within the App.tsx file

const app = createApp({
  features: [
    graphiqlPlugin,
    pagesPlugin,
    techRadarPlugin,
    techdocsPlugin,
    userSettingsPlugin,
    homePlugin,
    appVisualizerPlugin,
    scaffolderPlugin,
...

and declared (based on existing example) such a field declaration within the app-config.yaml file but that fails

    - scaffolder.page/fields: '@backstage/plugin-scaffolder#LowerCaseValuePickerFieldExtension'

Error reported "Invalid extension configuration at app.extensions[19][scaffolder.page/fields], value must be a boolean or object"

Raised by this code:

// backstage/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts)
  if (typeof value !== 'object' || Array.isArray(value)) {
    // We don't mention null here - we don't want people to explicitly enter
    // - entity.card.about: null
    throw new Error(errorMsg('value must be a boolean or object', id));
  }

@cmoulliard
Copy link
Author

cmoulliard commented Apr 12, 2024

I digged into the code and found how backstage is able to discover the custom template fields ;-)

Such a mechanism is taking place here within the scaffolderpage

/**
 * The Router and main entrypoint to the Scaffolder plugin.
 *
 * @public
 */
export const ScaffolderPage = scaffolderPlugin.provide(
  createRoutableExtension({
    name: 'ScaffolderPage',
    component: () => import('./components/Router').then(m => m.Router),
    mountPoint: rootRouteRef,
  }),
);

which creates a routableExtension using the Router.tsx component. The router.tsx contains a const instantiated from a function useCustomFieldExtensions() using either the react router dom => outlet() or props.children.

// https://github.com/backstage/backstage/blob/master/plugins/scaffolder/src/components/Router/Router.tsx#L110-L112
...
  const outlet = useOutlet() || props.children;
  const customFieldExtensions =
    useCustomFieldExtensions<FieldExtensionOptions>(outlet);
...

and

// https://github.com/backstage/backstage/blob/master/plugins/scaffolder-react/src/hooks/useCustomFieldExtensions.ts#L32-L39

import { useElementFilter } from '@backstage/core-plugin-api';
import { FieldExtensionOptions } from '../extensions';
import {
  FIELD_EXTENSION_KEY,
  FIELD_EXTENSION_WRAPPER_KEY,
} from '../extensions/keys';

/**
 * Hook that returns all custom field extensions from the current outlet.
 * @public
 */
export const useCustomFieldExtensions = <
  TComponentDataType = FieldExtensionOptions,
>(
  outlet: React.ReactNode,
) => {
  return useElementFilter(outlet, elements =>
    elements
      .selectByComponentData({
        key: FIELD_EXTENSION_WRAPPER_KEY,
      })
      .findComponentData<TComponentDataType>({
        key: FIELD_EXTENSION_KEY,
      }),
  );
}

Do you think that we could pass the props.children (or register the componentData within the outlet. How ? IDK) to the router using a new parameter created within the janus-idp front-end yaml file and containing ?

  • customField(s) name => to be loaded from plugin A
  • customField(s) name => to be loaded from plugin T
  • etc

@gashcrumb @tumido

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
jira Sync this issue with Jira kind/feature status/triage
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants