Skip to content
Open
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
@@ -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';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed

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: React.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 @@ -20,51 +20,41 @@
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<JsonObject>,
): FormValidation<JsonObject> => {
// 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');
}

Check warning on line 35 in workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using nullish coalescing operator (`??=`) instead of an assignment expression, as it is simpler to read.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZ-n8woQGYV1PN-QOm1t&open=AZ-n8woQGYV1PN-QOm1t&pullRequest=4024
const contentPromise = this.contentPromise;

return (FormComponent: React.ComponentType<FormDecoratorProps>) => {
return (props: OrchestratorFormContextProps) => {
const getExtraErrors = useGetExtraErrors();
const [DecoratorContent, setDecoratorContent] =
React.useState<React.ComponentType<any> | null>(null);

React.useEffect(() => {
let mounted = true;
contentPromise.then(m => {

Check failure on line 45 in workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZ-n8woQGYV1PN-QOm1u&open=AZ-n8woQGYV1PN-QOm1u&pullRequest=4024
if (mounted) setDecoratorContent(() => m.default);
});
return () => {

Check failure on line 48 in workspaces/orchestrator/plugins/orchestrator-form-widgets/src/FormWidgetsApi.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest functions more than 4 levels deep.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZ-n8woQGYV1PN-QOm1v&open=AZ-n8woQGYV1PN-QOm1v&pullRequest=4024
mounted = false;
};
}, []);

if (!DecoratorContent) {
return null;
}

return (
<FormComponent
widgets={widgets}
formContext={props}
customValidate={customValidate}
getExtraErrors={getExtraErrors}
/>
);
return <DecoratorContent FormComponent={FormComponent} {...props} />;
};
};
};
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
10 changes: 5 additions & 5 deletions workspaces/orchestrator/plugins/orchestrator/report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
19 changes: 17 additions & 2 deletions workspaces/orchestrator/plugins/orchestrator/src/alpha.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -58,7 +57,23 @@ const orchestratorPage = PageBlueprint.make({
path: '/orchestrator',
routeRef: orchestratorRootRouteRef,
title: 'Orchestrator',
icon: <OrchestratorIcon />,
icon: (
<svg

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the OrchestratorIcon import, just replace the OrchestratorIcon content with this one

xmlns="http://www.w3.org/2000/svg"
viewBox="7 7 24 24"
width="1em"
height="1em"
fill="currentColor"
>
<path d="M28,1H10C5.0294,1,1,5.0294,1,10v18c0,4.9706,4.0294,9,9,9h18c4.9706,0,9-4.0294,9-9V10c0-4.9706-4.0294-9-9-9ZM35.75,28c0,4.2734-3.4766,7.75-7.75,7.75H10c-4.2734,0-7.75-3.4766-7.75-7.75V10c0-4.2734,3.4766-7.75,7.75-7.75h18c4.2734,0,7.75,3.4766,7.75,7.75v18Z" />
<path d="M14.625,14v-4c0-.3452-.2803-.625-.625-.625h-4c-.3447,0-.625.2798-.625.625v4c0,.3452.2803.625.625.625h4c.3447,0,.625-.2798.625-.625ZM13.375,13.375h-2.75v-2.75h2.75v2.75Z" />
<path d="M27,22.6196c.1699,0,.3301-.0596.4395-.1797.1201-.1104.1904-.2803.1904-.4399,0-.0801-.0205-.1602-.0498-.2402-.0303-.0698-.0801-.1401-.1406-.1997-.2295-.2305-.6494-.2305-.8799,0-.1201.1099-.1797.2695-.1797.4399,0,.1699.0596.3296.1797.4399.1201.1201.2803.1797.4404.1797Z" />
<path d="M25,22.6196c.1699,0,.3301-.0596.4395-.1797.1201-.1104.1904-.2803.1904-.4399,0-.0801-.0205-.1602-.0498-.2402-.0303-.0698-.0801-.1401-.1406-.1997-.0498-.0601-.1299-.1104-.1992-.1401-.2305-.0903-.5107-.04-.6807.1401-.0596.0596-.1094.1299-.1396.1997-.0303.0801-.0498.1602-.0498.2402,0,.1597.0693.3296.1895.4399.1104.1201.2803.1797.4404.1797Z" />
<path d="M29,19.375h-8c-.3447,0-.625.2798-.625.625v8c0,.3452.2803.625.625.625h8c.3447,0,.625-.2798.625-.625v-8c0-.3452-.2803-.625-.625-.625ZM28.375,27.375h-6.75v-6.75h6.75v6.75Z" />
<path d="M16,12.625h5.5c1.0342,0,1.875.8413,1.875,1.875s-.8408,1.875-1.875,1.875h-2.875v-.375c0-.2529-.1523-.4805-.3857-.5776-.2314-.0952-.502-.0439-.6816.1357l-1,1c-.0129.0129-.0175.03-.0289.0438-.0412.0488-.0806.0994-.1054.1592-.0276.067-.039.1382-.0424.2096-.0005.0103-.0059.0189-.0059.0293,0,.0104.0055.0192.006.0295.0034.0713.0147.1423.0423.2094.0249.0601.0644.111.1058.16.0114.0135.0158.0304.0285.0431l1,1c.1201.1196.2803.1831.4424.1831.0811,0,.1621-.0156.2393-.0474.2334-.0972.3857-.3247.3857-.5776v-.375h2.875c1.7227,0,3.125-1.4019,3.125-3.125s-1.4023-3.125-3.125-3.125h-5.5c-.3447,0-.625.2798-.625.625s.2803.625.625.625Z" />
<path d="M17.4424,20.5581c-.2441-.2441-.6406-.2441-.8848,0-.2432.2441-.2432.6396,0,.8838l.9334.9331h-3.491c-1.3096,0-2.375-1.0654-2.375-2.375s1.0654-2.375,2.375-2.375h1c.3447,0,.625-.2798.625-.625s-.2803-.625-.625-.625h-1c-1.999,0-3.625,1.626-3.625,3.625s1.626,3.625,3.625,3.625h3.491l-.9334.9331c-.2432.2441-.2432.6396,0,.8838.1221.1221.2822.1831.4424.1831s.3203-.061.4424-.1831l2-2c.2432-.2441.2432-.6396,0-.8838l-2-2Z" />
</svg>
),
noHeader: true,
loader: () => import('./components/Router').then(m => <m.Router />),
},
Expand Down
Loading