Skip to content

Commit 8de836a

Browse files
guguclaude
andcommitted
feat: emit null on empty text widget input for nullable columns
Text widget now sends null to the backend when the user clears the field on a nullable column, and "" on a non-nullable column. Adds a new force_send_empty_string widget param (default false) to override the null conversion when callers want a literal empty string stored on a nullable column. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ae90d17 commit 8de836a

4 files changed

Lines changed: 64 additions & 4 deletions

File tree

frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,12 @@ export class DbTableWidgetsComponent implements OnInit {
230230
// isCurrency, isBtcAddress, isISO8601, isISO31661Alpha2, isISO31661Alpha3, isISO4217,
231231
// isDataURI, isMagnetURI, isMimeType, isLatLong, isSlug, isStrongPassword, isTaxID, isVAT
232232
// OR use "regex" with a regex parameter for custom pattern matching
233+
// force_send_empty_string: when true, always send "" to the backend on clear,
234+
// even if the column is nullable. Default false (cleared input becomes NULL on nullable columns).
233235
{
234236
"validate": null,
235-
"regex": null
237+
"regex": null,
238+
"force_send_empty_string": false
236239
}`,
237240
Textarea: `// provide number of strings to show.
238241
{

frontend/src/app/components/ui-components/record-edit-fields/text/text.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
[validateType]="validateType"
99
[regexPattern]="regexPattern"
1010
attr.data-testid="record-{{label()}}-text"
11-
[(ngModel)]="value" (ngModelChange)="onFieldChange.emit($event)">
12-
@if (maxLength && maxLength > 0 && value() && (maxLength - value().length) < 100) {
11+
[(ngModel)]="value" (ngModelChange)="handleValueChange($event)">
12+
@if (maxLength && maxLength > 0 && value() && 100 > (maxLength - value().length)) {
1313
<div matSuffix class="counter">{{value().length}} / {{maxLength}}</div>
1414
}
1515
@if (textField.errors?.['required']) {

frontend/src/app/components/ui-components/record-edit-fields/text/text.component.spec.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ describe('TextEditComponent', () => {
4646
});
4747

4848
it('should parse regexPattern from widget params', () => {
49-
fixture.componentRef.setInput('widgetStructure', { widget_params: { validate: 'regex', regex: '^[a-z]+$' } } as any);
49+
fixture.componentRef.setInput('widgetStructure', {
50+
widget_params: { validate: 'regex', regex: '^[a-z]+$' },
51+
} as any);
5052
component.ngOnInit();
5153
expect(component.regexPattern).toBe('^[a-z]+$');
5254
});
@@ -80,4 +82,49 @@ describe('TextEditComponent', () => {
8082
component.validateType = 'customValidator';
8183
expect(component.getValidationErrorMessage()).toBe('Invalid customValidator');
8284
});
85+
86+
it('should default forceSendEmptyString to false when widget_params omits the key', () => {
87+
fixture.componentRef.setInput('widgetStructure', { widget_params: { validate: null, regex: null } } as any);
88+
component.ngOnInit();
89+
expect(component.forceSendEmptyString).toBe(false);
90+
});
91+
92+
it('should emit null on empty input when column is nullable and force_send_empty_string is not set', () => {
93+
fixture.componentRef.setInput('structure', { allow_null: true } as any);
94+
component.ngOnInit();
95+
const emitted: any[] = [];
96+
component.onFieldChange.subscribe((v) => emitted.push(v));
97+
component.handleValueChange('');
98+
expect(emitted).toEqual([null]);
99+
});
100+
101+
it('should emit empty string on empty input when column is not nullable', () => {
102+
fixture.componentRef.setInput('structure', { allow_null: false } as any);
103+
component.ngOnInit();
104+
const emitted: any[] = [];
105+
component.onFieldChange.subscribe((v) => emitted.push(v));
106+
component.handleValueChange('');
107+
expect(emitted).toEqual(['']);
108+
});
109+
110+
it('should emit empty string on empty input when force_send_empty_string is true even on nullable column', () => {
111+
fixture.componentRef.setInput('structure', { allow_null: true } as any);
112+
fixture.componentRef.setInput('widgetStructure', {
113+
widget_params: { force_send_empty_string: true },
114+
} as any);
115+
component.ngOnInit();
116+
const emitted: any[] = [];
117+
component.onFieldChange.subscribe((v) => emitted.push(v));
118+
component.handleValueChange('');
119+
expect(emitted).toEqual(['']);
120+
});
121+
122+
it('should emit non-empty value unchanged regardless of nullability', () => {
123+
fixture.componentRef.setInput('structure', { allow_null: true } as any);
124+
component.ngOnInit();
125+
const emitted: any[] = [];
126+
component.onFieldChange.subscribe((v) => emitted.push(v));
127+
component.handleValueChange('hello');
128+
expect(emitted).toEqual(['hello']);
129+
});
83130
});

frontend/src/app/components/ui-components/record-edit-fields/text/text.component.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class TextEditComponent extends BaseEditFieldComponent implements OnInit
2020
maxLength: number | null = null;
2121
validateType: string | null = null;
2222
regexPattern: string | null = null;
23+
forceSendEmptyString: boolean = false;
2324

2425
override ngOnInit(): void {
2526
super.ngOnInit();
@@ -35,9 +36,18 @@ export class TextEditComponent extends BaseEditFieldComponent implements OnInit
3536

3637
this.validateType = params.validate || null;
3738
this.regexPattern = params.regex || null;
39+
this.forceSendEmptyString = !!params.force_send_empty_string;
3840
}
3941
}
4042

43+
handleValueChange(v: string | null): void {
44+
if (v === '' && !this.forceSendEmptyString && this.structure()?.allow_null === true) {
45+
this.onFieldChange.emit(null);
46+
return;
47+
}
48+
this.onFieldChange.emit(v);
49+
}
50+
4151
getValidationErrorMessage(): string {
4252
if (!this.validateType) {
4353
return '';

0 commit comments

Comments
 (0)