diff --git a/workspaces/orchestrator/.changeset/lazy-load-nfs-sync.md b/workspaces/orchestrator/.changeset/lazy-load-nfs-sync.md new file mode 100644 index 00000000000..47aebd37af7 --- /dev/null +++ b/workspaces/orchestrator/.changeset/lazy-load-nfs-sync.md @@ -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. diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormDecoratorContent.tsx b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormDecoratorContent.tsx new file mode 100644 index 00000000000..5624ac16400 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormDecoratorContent.tsx @@ -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 React 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, +): FormValidation => { + return errors; +}; + +const widgets = { + SchemaUpdater, + ActiveTextInput, + ActiveText, + ActiveDropdown, + ActiveMultiSelect, +}; + +const FormDecoratorContent = ({ + FormComponent, + ...props +}: { + FormComponent: React.ComponentType; +} & OrchestratorFormContextProps) => { + const getExtraErrors = useGetExtraErrors(); + + return ( + + ); +}; + +export default FormDecoratorContent; diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.test.tsx b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.test.tsx index 68052a80ee2..571bbcdb73f 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.test.tsx +++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.test.tsx @@ -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'; @@ -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[] = []; @@ -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({ diff --git a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx index 8a409170a3c..0c56d69e714 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx +++ b/workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx @@ -20,51 +20,41 @@ import { 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'; - -const customValidate = ( - _formData: JsonObject | undefined, - errors: FormValidation, -): FormValidation => { - // Trigger synchronous field validation - return errors; -}; - -const widgets = { - SchemaUpdater, - ActiveTextInput, - ActiveText, - ActiveDropdown, - ActiveMultiSelect, -}; 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.'); + if (!this.contentPromise) { + this.contentPromise = import('./FormDecoratorContent'); + } + const contentPromise = this.contentPromise; + return (FormComponent: React.ComponentType) => { return (props: OrchestratorFormContextProps) => { - const getExtraErrors = useGetExtraErrors(); + const [DecoratorContent, setDecoratorContent] = + React.useState | null>(null); + + React.useEffect(() => { + let mounted = true; + contentPromise.then(m => { + if (mounted) setDecoratorContent(() => m.default); + }); + return () => { + mounted = false; + }; + }, []); + + if (!DecoratorContent) { + return null; + } - return ( - - ); + return ; }; }; }; diff --git a/workspaces/orchestrator/plugins/orchestrator/report-alpha.api.md b/workspaces/orchestrator/plugins/orchestrator/report-alpha.api.md index f6da73896d3..3256ed1be90 100644 --- a/workspaces/orchestrator/plugins/orchestrator/report-alpha.api.md +++ b/workspaces/orchestrator/plugins/orchestrator/report-alpha.api.md @@ -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; @@ -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; @@ -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; @@ -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; diff --git a/workspaces/orchestrator/plugins/orchestrator/report.api.md b/workspaces/orchestrator/plugins/orchestrator/report.api.md index e3053aef832..44cc2b5bed4 100644 --- a/workspaces/orchestrator/plugins/orchestrator/report.api.md +++ b/workspaces/orchestrator/plugins/orchestrator/report.api.md @@ -45,10 +45,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; @@ -61,11 +61,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; @@ -130,9 +130,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; @@ -143,10 +143,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; diff --git a/workspaces/orchestrator/plugins/orchestrator/src/alpha.tsx b/workspaces/orchestrator/plugins/orchestrator/src/alpha.tsx index 0c54fdc5527..363ed7b7e0d 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/alpha.tsx +++ b/workspaces/orchestrator/plugins/orchestrator/src/alpha.tsx @@ -35,7 +35,6 @@ import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; import { orchestratorApiRef, OrchestratorClient } from './api'; -import OrchestratorIcon from './components/OrchestratorIcon'; import { entityInstanceRouteRef, entityWorkflowRouteRef, @@ -58,7 +57,23 @@ const orchestratorPage = PageBlueprint.make({ path: '/orchestrator', routeRef: orchestratorRootRouteRef, title: 'Orchestrator', - icon: , + icon: ( + + + + + + + + + + ), noHeader: true, loader: () => import('./components/Router').then(m => ), },