diff --git a/spiffworkflow-frontend/src/helpers.test.tsx b/spiffworkflow-frontend/src/helpers.test.tsx index 5a7889a65..be2072502 100644 --- a/spiffworkflow-frontend/src/helpers.test.tsx +++ b/spiffworkflow-frontend/src/helpers.test.tsx @@ -3,6 +3,7 @@ import { isInteger, slugifyString, underscorizeString, + recursivelyNullifyUndefinedValuesInPlace, } from './helpers'; test('it can slugify a string', () => { @@ -29,3 +30,38 @@ test('it can validate numeric values', () => { expect(isInteger('1 2')).toEqual(false); expect(isInteger(2)).toEqual(true); }); + +test('it can replace undefined values in object with null', () => { + const sampleData = { + talentType: 'foo', + rating: 'bar', + contacts: { + gmeets: undefined, + zoom: undefined, + phone: 'baz', + awesome: false, + info: '', + }, + items: [ + undefined, + { + contacts: { + gmeets: undefined, + zoom: undefined, + phone: 'baz', + }, + }, + 'HEY', + ], + undefined, + }; + + expect((sampleData.items[1] as any).contacts.zoom).toEqual(undefined); + + const result = recursivelyNullifyUndefinedValuesInPlace(sampleData); + expect(result).toEqual(sampleData); + expect(result.items[1].contacts.zoom).toEqual(null); + expect(result.items[2]).toEqual('HEY'); + expect(result.contacts.awesome).toEqual(false); + expect(result.contacts.info).toEqual(''); +}); diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx index c816302c6..1a148d3c9 100644 --- a/spiffworkflow-frontend/src/helpers.tsx +++ b/spiffworkflow-frontend/src/helpers.tsx @@ -26,6 +26,24 @@ export const underscorizeString = (inputString: string) => { return slugifyString(inputString).replace(/-/g, '_'); }; +export const recursivelyNullifyUndefinedValuesInPlace = (obj: any) => { + if (obj === null || obj === undefined) { + return null; + } + if (Array.isArray(obj)) { + obj.forEach((value: any, index: number) => { + // eslint-disable-next-line no-param-reassign + obj[index] = recursivelyNullifyUndefinedValuesInPlace(value); + }); + } else if (typeof obj === 'object') { + Object.entries(obj).forEach(([key, value]) => { + // eslint-disable-next-line no-param-reassign + obj[key] = recursivelyNullifyUndefinedValuesInPlace(value); + }); + } + return obj; +}; + export const selectKeysFromSearchParams = (obj: any, keys: string[]) => { const newSearchParams: { [key: string]: string } = {}; keys.forEach((key: string) => { diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 75ffa9bfe..d9e670f76 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -15,7 +15,10 @@ import { import { Form } from '../rjsf/carbon_theme'; import HttpService from '../services/HttpService'; import useAPIError from '../hooks/UseApiError'; -import { modifyProcessIdentifierForPathParam } from '../helpers'; +import { + modifyProcessIdentifierForPathParam, + recursivelyNullifyUndefinedValuesInPlace, +} from '../helpers'; import { EventDefinition, Task } from '../interfaces'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import InstructionsForEndUser from '../components/InstructionsForEndUser'; @@ -119,9 +122,9 @@ export default function TaskShow() { delete dataToSubmit.isManualTask; // NOTE: rjsf sets blanks values to undefined and JSON.stringify removes keys with undefined values - // so there is no way to clear out a field that previously had a value. - // To resolve this, we could potentially go through the object that we are posting (either in here or in - // HttpService) and translate all undefined values to null. + // so we convert undefined values to null recursively so that we can unset values in form fields + recursivelyNullifyUndefinedValuesInPlace(dataToSubmit); + HttpService.makeCallToBackend({ path: `/tasks/${params.process_instance_id}/${params.task_id}${queryParams}`, successCallback: processSubmitResult,