Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions workspaces/orchestrator/.changeset/lazy-load-nfs-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
---

Reduce NFS Module Federation sync size by lazy-loading heavy dependencies.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { BackendFeature } from '@backstage/backend-plugin-api';

// @public
const orchestratorModuleLoki: BackendFeature;
export default orchestratorModuleLoki;

```
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { BackendFeature } from '@backstage/backend-plugin-api';

// @public
const orchestratorPlugin: BackendFeature;
export default orchestratorPlugin;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { ComponentType } from 'react';
import {
FormDecoratorProps,
OrchestratorFormContextProps,
} from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-api';
import { FormValidation } from '@rjsf/utils';
import { JsonObject } from '@backstage/types';

import {
SchemaUpdater,
ActiveTextInput,
ActiveText,
ActiveDropdown,
ActiveMultiSelect,
} from './widgets';
import { useGetExtraErrors } from './utils';

const customValidate = (
_formData: JsonObject | undefined,
errors: FormValidation<JsonObject>,
): FormValidation<JsonObject> => {
return errors;
};

const widgets = {
SchemaUpdater,
ActiveTextInput,
ActiveText,
ActiveDropdown,
ActiveMultiSelect,
};

const FormDecoratorContent = ({
FormComponent,
...props
}: {
FormComponent: ComponentType<FormDecoratorProps>;
} & OrchestratorFormContextProps) => {
const getExtraErrors = useGetExtraErrors();

return (
<FormComponent
widgets={widgets}
formContext={props}
customValidate={customValidate}
getExtraErrors={getExtraErrors}
/>
);
};

export default FormDecoratorContent;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ComponentType } from 'react';
import { render } from '@testing-library/react';
import { act, render } from '@testing-library/react';
import { FormWidgetsApi } from './FormWidgetsApi';
import * as utils from './utils';

Expand All @@ -39,7 +39,10 @@ describe('FormWidgetsApi', () => {
expect(api.getReviewComponent?.()).toBeUndefined();
});

it('decorates form component with widgets and context props', () => {
it('decorates form component with widgets and context props', async () => {
// Pre-load the lazy module so import() resolves from cache
await import('./FormDecoratorContent');

const api = new FormWidgetsApi();
const receivedProps: Record<string, unknown>[] = [];

Expand All @@ -61,6 +64,10 @@ describe('FormWidgetsApi', () => {
/>,
);

// Flush the microtask queue so the dynamic import resolves
// and the state update re-renders the component
await act(async () => {});

expect(receivedProps).toHaveLength(1);
expect(receivedProps[0].widgets).toEqual(
expect.objectContaining({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,59 +14,61 @@
* limitations under the License.
*/

import React from 'react';
import { useEffect, useState } from 'react';
import type { ComponentType } from 'react';
import {
FormDecoratorProps,
OrchestratorFormApi,
OrchestratorFormContextProps,
} from '@red-hat-developer-hub/backstage-plugin-orchestrator-form-api';
import { FormValidation } from '@rjsf/utils';
import { JsonObject } from '@backstage/types';

import {
SchemaUpdater,
ActiveTextInput,
ActiveText,
ActiveDropdown,
ActiveMultiSelect,
} from './widgets';
import { useGetExtraErrors } from './utils';
function LazyDecoratorContent({
FormComponent,
contentPromise,
...props
}: {
FormComponent: ComponentType<FormDecoratorProps>;
contentPromise: Promise<typeof import('./FormDecoratorContent')>;
} & OrchestratorFormContextProps) {
const [Content, setContent] = useState<ComponentType<any> | null>(null);

useEffect(() => {
let mounted = true;
contentPromise.then(m => {
if (mounted) setContent(() => m.default);
});
return () => {
mounted = false;
};
}, [contentPromise]);

const customValidate = (
_formData: JsonObject | undefined,
errors: FormValidation<JsonObject>,
): FormValidation<JsonObject> => {
// Trigger synchronous field validation
return errors;
};
if (!Content) {
return null;
}

const widgets = {
SchemaUpdater,
ActiveTextInput,
ActiveText,
ActiveDropdown,
ActiveMultiSelect,
};
return <Content FormComponent={FormComponent} {...props} />;
}

export class FormWidgetsApi implements OrchestratorFormApi {
private contentPromise: Promise<
typeof import('./FormDecoratorContent')
> | null = null;

getFormDecorator: OrchestratorFormApi['getFormDecorator'] = () => {
// eslint-disable-next-line no-console
console.log('Using FormWidgetsApi by RHDH orchestrator-form-widgets.');

return (FormComponent: React.ComponentType<FormDecoratorProps>) => {
return (props: OrchestratorFormContextProps) => {
const getExtraErrors = useGetExtraErrors();
this.contentPromise ??= import('./FormDecoratorContent');
const contentPromise = this.contentPromise;

return (
<FormComponent
widgets={widgets}
formContext={props}
customValidate={customValidate}
getExtraErrors={getExtraErrors}
/>
);
};
};
return (FormComponent: ComponentType<FormDecoratorProps>) =>
(props: OrchestratorFormContextProps) => (
<LazyDecoratorContent
FormComponent={FormComponent}
contentPromise={contentPromise}
{...props}
/>
);
};

getReviewComponent: OrchestratorFormApi['getReviewComponent'] = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,10 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'table.headers.description': string;
readonly 'table.headers.version': string;
readonly 'table.headers.duration': string;
readonly 'table.headers.status': string;
readonly 'table.headers.entity': string;
readonly 'table.headers.runStatus': string;
readonly 'table.headers.started': string;
readonly 'table.headers.status': string;
readonly 'table.headers.workflowStatus': string;
readonly 'table.headers.lastRun': string;
readonly 'table.headers.lastRunStatus': string;
Expand All @@ -244,11 +244,11 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'table.actions.viewRuns': string;
readonly 'table.actions.viewInputSchema': string;
readonly 'table.actions.viewRunVariables': string;
readonly 'table.filters.placeholder': string;
readonly 'table.filters.status': string;
readonly 'table.filters.entity': string;
readonly 'table.filters.started': string;
readonly 'table.filters.status': string;
readonly 'table.filters.runBy': string;
readonly 'table.filters.placeholder': string;
readonly 'table.filters.clearAll': string;
readonly 'table.filters.startedOptions.today': string;
readonly 'table.filters.startedOptions.yesterday': string;
Expand Down Expand Up @@ -313,9 +313,9 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'run.logs.noLogsAvailable': string;
readonly 'run.abort.button': string;
readonly 'run.abort.title': string;
readonly 'run.abort.warning': string;
readonly 'run.abort.completed.title': string;
readonly 'run.abort.completed.message': string;
readonly 'run.abort.warning': string;
readonly 'run.retrigger': string;
readonly 'run.viewVariables': string;
readonly 'run.suggestedNextWorkflow': string;
Expand All @@ -326,10 +326,10 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'workflow.errors.abortFailed': string;
readonly 'workflow.errors.abortFailedWithReason': string;
readonly 'workflow.errors.failedToLoadDetails': string;
readonly 'workflow.definition': string;
readonly 'workflow.status.available': string;
readonly 'workflow.status.unavailable': string;
readonly 'workflow.successRatio': string;
readonly 'workflow.definition': string;
readonly 'workflow.inputSchema': string;
readonly 'workflow.inputSchemaDescription': string;
readonly 'workflow.successRatioDescription': string;
Expand Down
16 changes: 9 additions & 7 deletions workspaces/orchestrator/plugins/orchestrator/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { RouteRef } from '@backstage/core-plugin-api';
import { SvgIconProps } from '@mui/material/SvgIcon';
import type { SVGProps } from 'react';
import { TranslationRef } from '@backstage/frontend-plugin-api';
import { TranslationResource } from '@backstage/frontend-plugin-api';

Expand All @@ -20,7 +20,9 @@ export const IsOrchestratorCatalogTabAvailable: (entity: Entity) => boolean;
export const OrchestratorCatalogTab: () => JSX_2.Element;

// @public
export const OrchestratorIcon: (props: SvgIconProps) => JSX_2.Element;
export const OrchestratorIcon: (
props: SVGProps<SVGSVGElement>,
) => JSX_2.Element;

// @public
export const OrchestratorPage: () => JSX_2.Element;
Expand All @@ -45,10 +47,10 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'table.headers.description': string;
readonly 'table.headers.version': string;
readonly 'table.headers.duration': string;
readonly 'table.headers.status': string;
readonly 'table.headers.entity': string;
readonly 'table.headers.runStatus': string;
readonly 'table.headers.started': string;
readonly 'table.headers.status': string;
readonly 'table.headers.workflowStatus': string;
readonly 'table.headers.lastRun': string;
readonly 'table.headers.lastRunStatus': string;
Expand All @@ -61,11 +63,11 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'table.actions.viewRuns': string;
readonly 'table.actions.viewInputSchema': string;
readonly 'table.actions.viewRunVariables': string;
readonly 'table.filters.placeholder': string;
readonly 'table.filters.status': string;
readonly 'table.filters.entity': string;
readonly 'table.filters.started': string;
readonly 'table.filters.status': string;
readonly 'table.filters.runBy': string;
readonly 'table.filters.placeholder': string;
readonly 'table.filters.clearAll': string;
readonly 'table.filters.startedOptions.today': string;
readonly 'table.filters.startedOptions.yesterday': string;
Expand Down Expand Up @@ -130,9 +132,9 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'run.logs.noLogsAvailable': string;
readonly 'run.abort.button': string;
readonly 'run.abort.title': string;
readonly 'run.abort.warning': string;
readonly 'run.abort.completed.title': string;
readonly 'run.abort.completed.message': string;
readonly 'run.abort.warning': string;
readonly 'run.retrigger': string;
readonly 'run.viewVariables': string;
readonly 'run.suggestedNextWorkflow': string;
Expand All @@ -143,10 +145,10 @@ export const orchestratorTranslationRef: TranslationRef<
readonly 'workflow.errors.abortFailed': string;
readonly 'workflow.errors.abortFailedWithReason': string;
readonly 'workflow.errors.failedToLoadDetails': string;
readonly 'workflow.definition': string;
readonly 'workflow.status.available': string;
readonly 'workflow.status.unavailable': string;
readonly 'workflow.successRatio': string;
readonly 'workflow.definition': string;
readonly 'workflow.inputSchema': string;
readonly 'workflow.inputSchemaDescription': string;
readonly 'workflow.successRatioDescription': string;
Expand Down
Loading
Loading