Skip to content
Merged

4768 #4771

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
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,10 @@ const WorkflowCodeEditorSheet = ({

<SheetClose asChild>
<Button
className="mb-2.5 ml-1"
className="[&_svg]:size-5"
icon={<XIcon />}
onClick={() => handleOpenChange(false)}
size="iconXs"
size="icon"
variant="ghost"
/>
</SheetClose>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,20 @@ describe('PropertyInput', async () => {

expect(input).toHaveValue('=someValue');
});

it('uses minute precision (step=60) for time inputs so the field is easily clearable', () => {
const {container} = render(<PropertyInput aria-label="Time" label="Time" name="time" type="time" />);

const input = container.querySelector('input[type="time"]');

expect(input).toHaveAttribute('step', '60');
});

it('keeps step=1 for non-time inputs', () => {
const {container} = render(<PropertyInput aria-label="Date" label="Date" name="date" type="date" />);

const input = container.querySelector('input[type="date"]');
Comment thread
ivicac marked this conversation as resolved.

expect(input).toHaveAttribute('step', '1');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const PropertyInput = forwardRef<HTMLInputElement, PropertyInputProps>(
placeholder={placeholder}
ref={ref}
required={required}
step={1}
step={type === 'time' ? 60 : 1}
type={type}
value={localValue}
{...props}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {describe, expect, it} from 'vitest';

/**
* Replicates the resolvedValue branch of saveInputValue in useProperty.ts.
*
* Why: issue #4768 — clearing a Date/Datetime/Time field previously persisted an
* empty string. The backend expects null to represent "unset" for date/time
* fields, so the save path coerces empty string to null for those control types
* only (other string fields legitimately store "").
*/
const resolveSaveValue = ({
controlType,
isNumericalInput,
valueToSave,
}: {
controlType?: string;
isNumericalInput: boolean;
valueToSave: unknown;
}): unknown => {
const isDateOrTimeControlType = controlType === 'DATE' || controlType === 'DATE_TIME' || controlType === 'TIME';

if (isNumericalInput) {
return parseFloat(valueToSave as string);
}

if (valueToSave === '' && isDateOrTimeControlType) {
return null;
}

return valueToSave;
};

describe('saveInputValue resolved value', () => {
it('returns null when a DATE field is cleared', () => {
expect(resolveSaveValue({controlType: 'DATE', isNumericalInput: false, valueToSave: ''})).toBeNull();
});

it('returns null when a DATE_TIME field is cleared', () => {
expect(resolveSaveValue({controlType: 'DATE_TIME', isNumericalInput: false, valueToSave: ''})).toBeNull();
});

it('returns null when a TIME field is cleared', () => {
expect(resolveSaveValue({controlType: 'TIME', isNumericalInput: false, valueToSave: ''})).toBeNull();
});

it('preserves non-empty date values', () => {
expect(resolveSaveValue({controlType: 'DATE', isNumericalInput: false, valueToSave: '2026-04-15'})).toBe(
'2026-04-15'
);
});

it('preserves empty string for regular text fields', () => {
expect(resolveSaveValue({controlType: 'TEXT', isNumericalInput: false, valueToSave: ''})).toBe('');
});

it('preserves empty string when controlType is undefined', () => {
expect(resolveSaveValue({isNumericalInput: false, valueToSave: ''})).toBe('');
});

it('parses numerical input to float regardless of control type', () => {
expect(resolveSaveValue({controlType: 'INTEGER', isNumericalInput: true, valueToSave: '42'})).toBe(42);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -565,14 +565,26 @@

isSavingRef.current = true;

const isDateOrTimeControlType = controlType === 'DATE' || controlType === 'DATE_TIME' || controlType === 'TIME';

let resolvedValue: unknown;

if (isNumericalInput) {
Comment thread
ivicac marked this conversation as resolved.
resolvedValue = parseFloat(valueToSave as string);

Check warning on line 573 in client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts

View check run for this annotation

SonarQubeCloud / [client] SonarCloud Code Analysis

Prefer `Number.parseFloat` over `parseFloat`.

See more on https://sonarcloud.io/project/issues?id=bytechef_client&issues=AZ2Sf_B1BOENhfW4hJU_&open=AZ2Sf_B1BOENhfW4hJU_&pullRequest=4771
} else if (valueToSave === '' && isDateOrTimeControlType) {
resolvedValue = null;
Comment thread
ivicac marked this conversation as resolved.
} else {
resolvedValue = valueToSave;
}

saveProperty({
includeInMetadata: custom,
path,
successCallback: () => (isSavingRef.current = false),
type,
updateClusterElementParameterMutation,
updateWorkflowNodeParameterMutation,
value: isNumericalInput ? parseFloat(valueToSave as string) : valueToSave,
value: resolvedValue,
workflowId: workflow.id!,
});
}, 600);
Expand Down
Loading