From b2fa817c4e0403daf46dcd34813617d443433bf6 Mon Sep 17 00:00:00 2001 From: Lokananda Prabhu Date: Thu, 22 Jan 2026 12:56:56 +0530 Subject: [PATCH] Add omitFromWorkflowInput handling and review toggle --- .../omit-hidden-fields-review-toggle.md | 7 ++ .../src/components/OrchestratorForm.tsx | 10 ++- .../src/components/ReviewStep.tsx | 46 ++++++++-- .../src/utils/generateReviewTableData.test.ts | 32 +++++++ .../src/utils/generateReviewTableData.ts | 20 ++++- .../src/utils/pruneFormData.test.ts | 61 ++++++++++++- .../src/utils/pruneFormData.ts | 85 +++++++++++++++++++ .../plugins/orchestrator/report.api.md | 3 +- .../orchestrator/src/translations/de.ts | 3 +- .../orchestrator/src/translations/es.ts | 3 +- .../orchestrator/src/translations/fr.ts | 3 + .../orchestrator/src/translations/it.ts | 3 + .../orchestrator/src/translations/ja.ts | 3 + .../orchestrator/src/translations/ref.ts | 4 +- 14 files changed, 266 insertions(+), 17 deletions(-) create mode 100644 workspaces/orchestrator/.changeset/omit-hidden-fields-review-toggle.md diff --git a/workspaces/orchestrator/.changeset/omit-hidden-fields-review-toggle.md b/workspaces/orchestrator/.changeset/omit-hidden-fields-review-toggle.md new file mode 100644 index 0000000000..afecdfb26f --- /dev/null +++ b/workspaces/orchestrator/.changeset/omit-hidden-fields-review-toggle.md @@ -0,0 +1,7 @@ +--- +'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator': patch +--- + +Exclude omitFromWorkflowInput fields from execution payloads and add a review +toggle to show hidden parameters. diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx index ede2cbb12e..6298ac2f4c 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx @@ -28,7 +28,7 @@ import { OrchestratorFormContextProps } from '@red-hat-developer-hub/backstage-p import { TranslationFunction } from '../hooks/useTranslation'; import extractStaticDefaults from '../utils/extractStaticDefaults'; import generateUiSchema from '../utils/generateUiSchema'; -import { pruneFormData } from '../utils/pruneFormData'; +import { omitFromWorkflowInput, pruneFormData } from '../utils/pruneFormData'; import { StepperContextProvider } from '../utils/StepperContext'; import OrchestratorFormWrapper from './OrchestratorFormWrapper'; import ReviewStep from './ReviewStep'; @@ -149,10 +149,14 @@ const OrchestratorForm = ({ return pruneFormData(formData, schema); }, [formData, schema]); + const workflowInputData = useMemo(() => { + return omitFromWorkflowInput(prunedFormData, schema); + }, [prunedFormData, schema]); + const _handleExecute = useCallback(() => { // Use pruned data for execution to avoid submitting stale properties - handleExecute(prunedFormData); - }, [prunedFormData, handleExecute]); + handleExecute(workflowInputData); + }, [workflowInputData, handleExecute]); const onSubmit = useCallback( (_formData: JsonObject) => { diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx index 99d4233fad..69d872cccd 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/components/ReviewStep.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { Content } from '@backstage/core-components'; import { JsonObject } from '@backstage/types'; @@ -22,7 +22,10 @@ import { JsonObject } from '@backstage/types'; import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; +import FormControlLabel from '@mui/material/FormControlLabel'; import Paper from '@mui/material/Paper'; +import Switch from '@mui/material/Switch'; +import Typography from '@mui/material/Typography'; import type { JSONSchema7 } from 'json-schema'; import { get } from 'lodash'; import { makeStyles } from 'tss-react/mui'; @@ -60,6 +63,12 @@ const useStyles = makeStyles()(theme => ({ hiddenFieldsAlert: { marginBottom: theme.spacing(2), }, + hiddenFieldsAction: { + marginLeft: theme.spacing(2), + }, + hiddenFieldsText: { + fontSize: theme.typography.body1.fontSize, + }, })); /** @@ -115,9 +124,12 @@ const ReviewStep = ({ const { classes } = useStyles(); const { handleBack } = useStepperContext(); + const [showHiddenFields, setShowHiddenFields] = useState(false); const displayData = useMemo(() => { - return generateReviewTableData(schema, data); - }, [schema, data]); + return generateReviewTableData(schema, data, { + includeHiddenFields: showHiddenFields, + }); + }, [schema, data, showHiddenFields]); const showHiddenFieldsNote = useMemo(() => { return hasHiddenFields(schema); @@ -127,8 +139,32 @@ const ReviewStep = ({ {showHiddenFieldsNote && ( - - {t('reviewStep.hiddenFieldsNote')} + + setShowHiddenFields(event.target.checked) + } + color="primary" + /> + } + label={ + + {t('reviewStep.showHiddenParameters')} + + } + /> + } + > + + {t('reviewStep.hiddenFieldsNote')} + )} diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts index c93763cc62..5541bda745 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.test.ts @@ -204,6 +204,38 @@ describe('mapSchemaToData', () => { expect(result).toEqual(expectedResult); }); + it('should include hidden fields when includeHiddenFields is true', () => { + const schema: JSONSchema7 = { + type: 'object', + properties: { + visibleField: { + type: 'string', + title: 'Visible Field', + }, + hiddenField: { + type: 'string', + title: 'Hidden Field', + 'ui:hidden': true, + } as JSONSchema7, + }, + }; + + const data = { + visibleField: 'shown', + hiddenField: 'should appear', + }; + + const expectedResult = { + 'Visible Field': 'shown', + 'Hidden Field': 'should appear', + }; + + const result = generateReviewTableData(schema, data, { + includeHiddenFields: true, + }); + expect(result).toEqual(expectedResult); + }); + it('should exclude nested hidden fields from review table', () => { const schema: JSONSchema7 = { type: 'object', diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts index e087c70b85..1be463c850 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/generateReviewTableData.ts @@ -29,6 +29,7 @@ export function processSchema( value: JsonValue | undefined, schema: JSONSchema7, formState: JsonObject, + includeHiddenFields: boolean, ): JsonObject { const parsedSchema = new JSONSchema(schema); const definitionInSchema = @@ -43,7 +44,7 @@ export function processSchema( if (definitionInSchema) { // Skip hidden fields in the review table const uiHidden = definitionInSchema['ui:hidden']; - if (uiHidden !== undefined) { + if (!includeHiddenFields && uiHidden !== undefined) { // Handle both static boolean and condition objects const hiddenCondition = uiHidden as HiddenCondition; const isHidden = evaluateHiddenCondition(hiddenCondition, formState); @@ -63,7 +64,13 @@ export function processSchema( const curKey = key ? `${key}/${nestedKey}` : nestedKey; return { ...prev, - ...processSchema(curKey, _nestedValue, schema, formState), + ...processSchema( + curKey, + _nestedValue, + schema, + formState, + includeHiddenFields, + ), }; }, {}, @@ -84,9 +91,16 @@ export function processSchema( function generateReviewTableData( schema: JSONSchema7, data: JsonObject, + options?: { includeHiddenFields?: boolean }, ): JsonObject { schema.title = ''; - const result = processSchema('', data, schema, data); + const result = processSchema( + '', + data, + schema, + data, + options?.includeHiddenFields ?? false, + ); return result[''] as JsonObject; } diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.test.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.test.ts index b464241662..8d8f2e0b0b 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.test.ts +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.test.ts @@ -18,7 +18,7 @@ import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; -import { pruneFormData } from './pruneFormData'; +import { omitFromWorkflowInput, pruneFormData } from './pruneFormData'; describe('pruneFormData', () => { describe('Basic Property Handling', () => { @@ -726,4 +726,63 @@ describe('pruneFormData', () => { expect(result.step1).not.toHaveProperty('advancedField2'); }); }); + + describe('omitFromWorkflowInput', () => { + it('should remove properties marked with omitFromWorkflowInput', () => { + const schema: JSONSchema7 = { + type: 'object', + properties: { + visible: { type: 'string' }, + hidden: { + type: 'string', + omitFromWorkflowInput: true, + } as JSONSchema7, + }, + }; + + const formData = { + visible: 'keep', + hidden: 'omit', + }; + + const result = omitFromWorkflowInput(formData, schema); + + expect(result).toEqual({ visible: 'keep' }); + expect(result).not.toHaveProperty('hidden'); + }); + + it('should remove nested properties marked with omitFromWorkflowInput', () => { + const schema: JSONSchema7 = { + type: 'object', + properties: { + step: { + type: 'object', + properties: { + visible: { type: 'string' }, + secret: { + type: 'string', + omitFromWorkflowInput: true, + } as JSONSchema7, + }, + }, + }, + }; + + const formData = { + step: { + visible: 'keep', + secret: 'omit', + }, + }; + + const result = omitFromWorkflowInput(formData, schema); + + expect(result).toEqual({ + step: { + visible: 'keep', + }, + }); + expect(result.step).not.toHaveProperty('secret'); + }); + }); }); diff --git a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.ts b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.ts index 8093894d1a..1276eca808 100644 --- a/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.ts +++ b/workspaces/orchestrator/plugins/orchestrator-form-react/src/utils/pruneFormData.ts @@ -18,6 +18,10 @@ import { JsonObject, JsonValue } from '@backstage/types'; import type { JSONSchema7 } from 'json-schema'; +type WorkflowInputSchema = JSONSchema7 & { + omitFromWorkflowInput?: boolean; +}; + /** * Resolves $ref references in a schema by looking in $defs */ @@ -40,6 +44,10 @@ function resolveSchema( return schema; } +function shouldOmitFromWorkflowInput(schema: JSONSchema7): boolean { + return (schema as WorkflowInputSchema).omitFromWorkflowInput === true; +} + /** * Checks if a property's enum value matches the form data */ @@ -304,3 +312,80 @@ export function pruneFormData( return pruned; } + +/** + * Removes fields marked with omitFromWorkflowInput from the form data. + * This keeps the review UI intact while excluding data from execution payloads. + */ +export function omitFromWorkflowInput( + formData: JsonObject, + schema: JSONSchema7, + rootSchema?: JSONSchema7, +): JsonObject { + const root = rootSchema || schema; + const filtered: JsonObject = {}; + + for (const [key, value] of Object.entries(formData)) { + if (value === undefined) continue; + + let propSchema = schema.properties?.[key]; + if (typeof propSchema === 'boolean' || !propSchema) { + filtered[key] = value as JsonValue; + continue; + } + + propSchema = resolveSchema(propSchema as JSONSchema7, root); + if (shouldOmitFromWorkflowInput(propSchema)) { + continue; + } + + if ( + propSchema.type === 'object' && + typeof value === 'object' && + value !== null && + !Array.isArray(value) + ) { + filtered[key] = omitFromWorkflowInput( + value as JsonObject, + propSchema as JSONSchema7, + root, + ); + continue; + } + + if (propSchema.type === 'array' && Array.isArray(value)) { + const itemsSchema = + typeof propSchema.items === 'object' && propSchema.items + ? resolveSchema(propSchema.items as JSONSchema7, root) + : undefined; + + if (itemsSchema && shouldOmitFromWorkflowInput(itemsSchema)) { + continue; + } + + if ( + itemsSchema && + itemsSchema.type === 'object' && + itemsSchema.properties + ) { + filtered[key] = value.map(item => { + if (typeof item === 'object' && item !== null) { + return omitFromWorkflowInput( + item as JsonObject, + itemsSchema as JSONSchema7, + root, + ); + } + return item as JsonValue; + }); + } else { + filtered[key] = value as JsonValue; + } + continue; + } + + filtered[key] = value as JsonValue; + } + + return filtered; +} diff --git a/workspaces/orchestrator/plugins/orchestrator/report.api.md b/workspaces/orchestrator/plugins/orchestrator/report.api.md index d2603918ce..d2f933a264 100644 --- a/workspaces/orchestrator/plugins/orchestrator/report.api.md +++ b/workspaces/orchestrator/plugins/orchestrator/report.api.md @@ -164,6 +164,7 @@ readonly "tooltips.workflowDown": string; readonly "tooltips.suspended": string; readonly "tooltips.userNotAuthorizedAbort": string; readonly "reviewStep.hiddenFieldsNote": string; +readonly "reviewStep.showHiddenParameters": string; readonly "permissions.accessDenied": string; readonly "permissions.accessDeniedDescription": string; readonly "permissions.requiredPermission": string; @@ -185,7 +186,7 @@ export const orchestratorTranslations: TranslationResource<"plugin.orchestrator" // src/components/catalogComponents/CatalogTab.d.ts:1:22 - (ae-undocumented) Missing documentation for "IsOrchestratorCatalogTabAvailable". // src/components/catalogComponents/CatalogTab.d.ts:2:22 - (ae-undocumented) Missing documentation for "OrchestratorCatalogTab". // src/translations/index.d.ts:2:22 - (ae-undocumented) Missing documentation for "orchestratorTranslations". -// src/translations/ref.d.ts:200:22 - (ae-undocumented) Missing documentation for "orchestratorTranslationRef". +// src/translations/ref.d.ts:201:22 - (ae-undocumented) Missing documentation for "orchestratorTranslationRef". // (No @packageDocumentation comment for this package) diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/de.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/de.ts index 6e8980e03a..f0f111704b 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/de.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/de.ts @@ -148,7 +148,8 @@ const orchestratorTranslationDe = createTranslationMessages({ 'messages.additionalDetailsAboutThisErrorAreNotAvailable': 'Zusätzliche Informationen zu diesem Fehler sind nicht verfügbar', 'reviewStep.hiddenFieldsNote': - 'Einige Felder sind auf dieser Seite ausgeblendet, werden aber in die Workflow-Ausführungsanforderung aufgenommen.', + 'Einige Parameter sind auf dieser Seite ausgeblendet.', + 'reviewStep.showHiddenParameters': 'Verborgene Parameter anzeigen', 'common.close': 'Schließen', 'common.cancel': 'Abbrechen', 'common.execute': 'Ausführen', diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/es.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/es.ts index 0e64154462..7e3e1da8b8 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/es.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/es.ts @@ -149,7 +149,8 @@ const orchestratorTranslationEs = createTranslationMessages({ 'messages.additionalDetailsAboutThisErrorAreNotAvailable': 'No hay detalles adicionales sobre este error disponibles', 'reviewStep.hiddenFieldsNote': - 'Algunos campos están ocultos en esta página pero se incluirán en la solicitud de ejecución del flujo de trabajo.', + 'Algunos parámetros están ocultos en esta página.', + 'reviewStep.showHiddenParameters': 'Mostrar parámetros ocultos', 'common.close': 'Cerrar', 'common.cancel': 'Cancelar', 'common.execute': 'Ejecutar', diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/fr.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/fr.ts index 636ebccc9a..1f3ec3bed3 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/fr.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/fr.ts @@ -151,6 +151,9 @@ const orchestratorTranslationFr = createTranslationMessages({ "Ce workflow n'a pas de schéma JSON défini pour la validation des entrées. Vous pouvez toujours exécuter le workflow, mais la validation des entrées sera limitée.", 'messages.additionalDetailsAboutThisErrorAreNotAvailable': 'Des détails supplémentaires sur cette erreur ne sont pas disponibles', + 'reviewStep.hiddenFieldsNote': + 'Certains paramètres sont masqués sur cette page.', + 'reviewStep.showHiddenParameters': 'Afficher les paramètres masqués', 'common.close': 'Fermer', 'common.cancel': 'Annuler', 'common.execute': 'Exécuter', diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/it.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/it.ts index 5274c2829d..32908d1bcc 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/it.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/it.ts @@ -152,6 +152,9 @@ const orchestratorTranslationIt = createTranslationMessages({ "Nessuno schema JSON definito per la convalida dell'input di questo flusso di lavoro. È comunque possibile eseguire il flusso di lavoro, ma la convalida degli input sarà limitata.", 'messages.additionalDetailsAboutThisErrorAreNotAvailable': 'Non sono disponibili ulteriori dettagli su questo errore', + 'reviewStep.hiddenFieldsNote': + 'Alcuni parametri sono nascosti in questa pagina.', + 'reviewStep.showHiddenParameters': 'Mostra parametri nascosti', 'common.close': 'Chiudi', 'common.cancel': 'Cancella', 'common.execute': 'Esegui', diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/ja.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/ja.ts index 8da18d8584..4a1e544e49 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/ja.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/ja.ts @@ -146,6 +146,9 @@ const orchestratorTranslationJa = createTranslationMessages({ 'このワークフローには、入力検証用に定義された JSON スキーマがありません。ワークフローの実行は可能ですが、入力の検証は限定的になります。', 'messages.additionalDetailsAboutThisErrorAreNotAvailable': 'このエラーに関する追加情報はありません', + 'reviewStep.hiddenFieldsNote': + 'このページでは一部のパラメーターが非表示です。', + 'reviewStep.showHiddenParameters': '非表示のパラメーターを表示', 'common.close': '閉じる', 'common.cancel': 'キャンセル', 'common.execute': '実行', diff --git a/workspaces/orchestrator/plugins/orchestrator/src/translations/ref.ts b/workspaces/orchestrator/plugins/orchestrator/src/translations/ref.ts index 176ea44545..de942755df 100644 --- a/workspaces/orchestrator/plugins/orchestrator/src/translations/ref.ts +++ b/workspaces/orchestrator/plugins/orchestrator/src/translations/ref.ts @@ -176,8 +176,8 @@ export const orchestratorMessages = { 'Additional details about this error are not available', }, reviewStep: { - hiddenFieldsNote: - 'Some fields are hidden on this page but will be included in the workflow execution request.', + hiddenFieldsNote: 'Some parameters are hidden on this page.', + showHiddenParameters: 'Show hidden parameters', }, common: { close: 'Close',