Skip to content

Commit f8c5680

Browse files
committed
feat: make uiTransformations explicit
1 parent 5667af6 commit f8c5680

File tree

4 files changed

+101
-40
lines changed

4 files changed

+101
-40
lines changed

docs/readme-generic-ui.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ Each field definition supports the following properties:
7777
- `"textDecoration"`: Text decoration for the value (CSS text-decoration value)
7878
- `"textTransform"`: Text transformation for the value (CSS text-transform value)
7979
- `"displayAs"`: Controls how the value is displayed:
80-
- `'plainText'`: Render value as plain text without any built-in transformation
81-
- `'secret'`: Render value as a secret with show/hide hover
80+
- `'secret'`: Render value as a secret with show/hide toggle
81+
- `'boolIcon'`: Render boolean-like values (true/false, True/False, TRUE/FALSE) as icon indicators
82+
- `'link'`: Render URL values as clickable links (supports http://, https://, ftp://, mailto:, tel: protocols)
8283
- `"withCopyButton"`: Boolean flag to show a copy button next to the value for easy copying to clipboard
8384
- `"dynamicValuesDefinition"`: Configuration for dynamic value loading:
8485
- `"operation"`: GraphQL operation name
@@ -92,7 +93,8 @@ Below is an example content-configuration for an accounts node using the generic
9293
This example demonstrates various features including:
9394
- **Secret fields**: The "Key" field in `listView` and "API Key" field in `detailView` use `displayAs: "secret"` to hide sensitive data with a toggle
9495
- **Copy buttons**: Multiple fields include `withCopyButton: true` for easy copying to clipboard
95-
- **Plain text display**: The "External URL" field uses `displayAs: "plainText"` to prevent automatic link formatting
96+
- **Link display**: The "External URL" field uses `displayAs: "link"` to render URLs as clickable links
97+
- **Boolean display**: The "Active" field uses `displayAs: "boolIcon"` to show boolean values as icons
9698
- **Custom styling**: The "Type" and "Display Name" fields use `labelDisplay` for visual customization
9799
- **Field grouping**: Contact information is grouped using the `group` property
98100

@@ -216,10 +218,17 @@ This example demonstrates various features including:
216218
"label": "External URL",
217219
"property": "spec.externalUrl",
218220
"uiSettings": {
219-
"displayAs": "plainText",
221+
"displayAs": "link",
220222
"withCopyButton": true
221223
}
222224
},
225+
{
226+
"label": "Active",
227+
"property": "spec.active",
228+
"uiSettings": {
229+
"displayAs": "boolIcon"
230+
}
231+
},
223232
{
224233
"label": "Contact Info",
225234
"property": "spec.email",

projects/lib/models/models/resource.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Condition, ObjectMeta } from 'kubernetes-types/meta/v1';
22

3+
34
export interface LabelDisplay {
45
backgroundColor?: string;
56
color?: string;
@@ -23,7 +24,7 @@ export interface PropertyField {
2324

2425
export interface UiSettings {
2526
labelDisplay?: LabelDisplay | boolean;
26-
displayAs?: 'plainText' | 'secret';
27+
displayAs?: 'secret' | 'boolIcon' | 'link';
2728
withCopyButton?: boolean;
2829
}
2930

projects/wc/src/app/components/generic-ui/value-cell/value-cell.component.html

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@let displayType = displayAs();
12
<span [class.label-value]="labelDisplay()" [style]="{
23
backgroundColor: labelDisplay()?.backgroundColor,
34
color: labelDisplay()?.color,
@@ -6,20 +7,18 @@
67
textDecoration: labelDisplay()?.textDecoration,
78
textTransform: labelDisplay()?.textTransform,
89
}">
9-
@if (displayAs() === 'plainText') {
10-
{{ value() }}
11-
} @else if (displayAs() === 'secret') {
10+
@if (displayType === 'secret') {
1211
<wc-secret-value [value]="value()" [isVisible]="isVisible()"></wc-secret-value>
13-
} @else if (isBoolLike()) {
12+
} @else if (displayType === 'boolIcon' && isBoolLike()) {
1413
<wc-boolean-value [boolValue]="boolValue()!"></wc-boolean-value>
15-
} @else if (isUrlValue()) {
14+
} @else if (displayType === 'link' && isUrlValue()) {
1615
<wc-link-value [urlValue]="stringValue()!"></wc-link-value>
1716
} @else {
1817
{{ value() }}
1918
}
2019
</span>
2120

22-
@if (displayAs() === 'secret') {
21+
@if (displayType === 'secret') {
2322
<ui5-icon
2423
class="toggle-icon"
2524
[name]="isVisible() ? 'hide' : 'show'"

projects/wc/src/app/components/generic-ui/value-cell/value-cell.component.spec.ts

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ describe('ValueCellComponent', () => {
7171
});
7272

7373
it('should render boolean-value component for boolean-like values', () => {
74-
const { fixture } = makeComponent('true');
74+
const { fixture } = makeComponent('true', {
75+
uiSettings: { displayAs: 'boolIcon' },
76+
});
7577
const compiled = fixture.nativeElement;
7678

7779
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -80,7 +82,9 @@ describe('ValueCellComponent', () => {
8082
});
8183

8284
it('should render boolean-value component for false boolean-like values', () => {
83-
const { fixture } = makeComponent('false');
85+
const { fixture } = makeComponent('false', {
86+
uiSettings: { displayAs: 'boolIcon' },
87+
});
8488
const compiled = fixture.nativeElement;
8589

8690
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -89,7 +93,9 @@ describe('ValueCellComponent', () => {
8993
});
9094

9195
it('should render boolean-value component for actual boolean values', () => {
92-
const { fixture } = makeComponent(true);
96+
const { fixture } = makeComponent(true, {
97+
uiSettings: { displayAs: 'boolIcon' },
98+
});
9399
const compiled = fixture.nativeElement;
94100

95101
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -98,7 +104,9 @@ describe('ValueCellComponent', () => {
98104
});
99105

100106
it('should render link-value component for valid URLs', () => {
101-
const { fixture } = makeComponent('https://example.com');
107+
const { fixture } = makeComponent('https://example.com', {
108+
uiSettings: { displayAs: 'link' },
109+
});
102110
const compiled = fixture.nativeElement;
103111

104112
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -107,7 +115,9 @@ describe('ValueCellComponent', () => {
107115
});
108116

109117
it('should render link-value component for valid URLs with different protocols', () => {
110-
const { fixture } = makeComponent('http://test.com');
118+
const { fixture } = makeComponent('http://test.com', {
119+
uiSettings: { displayAs: 'link' },
120+
});
111121
const compiled = fixture.nativeElement;
112122

113123
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -146,7 +156,9 @@ describe('ValueCellComponent', () => {
146156
});
147157

148158
it('should render plain text for invalid URLs', () => {
149-
const { fixture } = makeComponent('not-a-url');
159+
const { fixture } = makeComponent('not-a-url', {
160+
uiSettings: { displayAs: 'link' },
161+
});
150162
const compiled = fixture.nativeElement;
151163

152164
expect(compiled.querySelector('wc-boolean-value')).toBeFalsy();
@@ -234,12 +246,12 @@ describe('ValueCellComponent', () => {
234246

235247
it('should not render secret-value component when displayAs is not secret', () => {
236248
const { fixture } = makeComponent('plain-text', {
237-
uiSettings: { displayAs: 'plainText' },
249+
uiSettings: { displayAs: 'link' },
238250
});
239251
const compiled = fixture.nativeElement;
240252

241253
expect(compiled.querySelector('wc-secret-value')).toBeFalsy();
242-
expect(component.displayAs()).toBe('plainText');
254+
expect(component.displayAs()).toBe('link');
243255
});
244256

245257
it('should not render secret-value component when displayAs is undefined', () => {
@@ -262,7 +274,7 @@ describe('ValueCellComponent', () => {
262274

263275
it('should not render toggle icon when displayAs is not secret', () => {
264276
const { fixture } = makeComponent('plain-text', {
265-
uiSettings: { displayAs: 'plainText' },
277+
uiSettings: { displayAs: 'link' },
266278
});
267279
const compiled = fixture.nativeElement;
268280

@@ -412,9 +424,9 @@ describe('ValueCellComponent', () => {
412424
});
413425

414426
describe('displayAs plainText functionality', () => {
415-
it('should render plain text when displayAs is plainText', () => {
427+
it('should render plain text when displayAs is undefined', () => {
416428
const { fixture } = makeComponent('test-value', {
417-
uiSettings: { displayAs: 'plainText' },
429+
uiSettings: { displayAs: undefined },
418430
});
419431
const compiled = fixture.nativeElement;
420432

@@ -426,7 +438,7 @@ describe('ValueCellComponent', () => {
426438

427439
it('should not render plain text when displayAs is not plainText', () => {
428440
const { fixture } = makeComponent('https://example.com', {
429-
uiSettings: {},
441+
uiSettings: { displayAs: 'link' },
430442
});
431443
const compiled = fixture.nativeElement;
432444

@@ -436,7 +448,9 @@ describe('ValueCellComponent', () => {
436448

437449
describe('boolean normalization edge cases', () => {
438450
it('should handle boolean true value', () => {
439-
const { fixture } = makeComponent(true);
451+
const { fixture } = makeComponent(true, {
452+
uiSettings: { displayAs: 'boolIcon' },
453+
});
440454
const compiled = fixture.nativeElement;
441455

442456
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -445,7 +459,9 @@ describe('ValueCellComponent', () => {
445459
});
446460

447461
it('should handle boolean false value', () => {
448-
const { fixture } = makeComponent(false);
462+
const { fixture } = makeComponent(false, {
463+
uiSettings: { displayAs: 'boolIcon' },
464+
});
449465
const compiled = fixture.nativeElement;
450466

451467
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -454,7 +470,9 @@ describe('ValueCellComponent', () => {
454470
});
455471

456472
it('should handle string "True" (capitalized)', () => {
457-
const { fixture } = makeComponent('True');
473+
const { fixture } = makeComponent('True', {
474+
uiSettings: { displayAs: 'boolIcon' },
475+
});
458476
const compiled = fixture.nativeElement;
459477

460478
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -463,7 +481,9 @@ describe('ValueCellComponent', () => {
463481
});
464482

465483
it('should handle string "FALSE" (uppercase)', () => {
466-
const { fixture } = makeComponent('FALSE');
484+
const { fixture } = makeComponent('FALSE', {
485+
uiSettings: { displayAs: 'boolIcon' },
486+
});
467487
const compiled = fixture.nativeElement;
468488

469489
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -472,7 +492,9 @@ describe('ValueCellComponent', () => {
472492
});
473493

474494
it('should handle string "TRUE" (uppercase)', () => {
475-
const { fixture } = makeComponent('TRUE');
495+
const { fixture } = makeComponent('TRUE', {
496+
uiSettings: { displayAs: 'boolIcon' },
497+
});
476498
const compiled = fixture.nativeElement;
477499

478500
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -481,7 +503,9 @@ describe('ValueCellComponent', () => {
481503
});
482504

483505
it('should not treat "yes" as boolean', () => {
484-
const { fixture } = makeComponent('yes');
506+
const { fixture } = makeComponent('yes', {
507+
uiSettings: { displayAs: 'boolIcon' },
508+
});
485509
const compiled = fixture.nativeElement;
486510

487511
expect(compiled.querySelector('wc-boolean-value')).toBeFalsy();
@@ -518,7 +542,9 @@ describe('ValueCellComponent', () => {
518542

519543
it('should handle object with toString method returning "true"', () => {
520544
const obj = { toString: () => 'true' };
521-
const { fixture } = makeComponent(obj);
545+
const { fixture } = makeComponent(obj, {
546+
uiSettings: { displayAs: 'boolIcon' },
547+
});
522548
const compiled = fixture.nativeElement;
523549

524550
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -528,7 +554,9 @@ describe('ValueCellComponent', () => {
528554

529555
it('should handle object with toString method returning "false"', () => {
530556
const obj = { toString: () => 'false' };
531-
const { fixture } = makeComponent(obj);
557+
const { fixture } = makeComponent(obj, {
558+
uiSettings: { displayAs: 'boolIcon' },
559+
});
532560
const compiled = fixture.nativeElement;
533561

534562
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();
@@ -539,7 +567,9 @@ describe('ValueCellComponent', () => {
539567

540568
describe('URL validation edge cases', () => {
541569
it('should handle valid HTTPS URL', () => {
542-
const { fixture } = makeComponent('https://example.com');
570+
const { fixture } = makeComponent('https://example.com', {
571+
uiSettings: { displayAs: 'link' },
572+
});
543573
const compiled = fixture.nativeElement;
544574

545575
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -548,7 +578,9 @@ describe('ValueCellComponent', () => {
548578
});
549579

550580
it('should handle valid HTTP URL', () => {
551-
const { fixture } = makeComponent('http://example.com');
581+
const { fixture } = makeComponent('http://example.com', {
582+
uiSettings: { displayAs: 'link' },
583+
});
552584
const compiled = fixture.nativeElement;
553585

554586
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -557,7 +589,9 @@ describe('ValueCellComponent', () => {
557589
});
558590

559591
it('should handle valid FTP URL', () => {
560-
const { fixture } = makeComponent('ftp://example.com');
592+
const { fixture } = makeComponent('ftp://example.com', {
593+
uiSettings: { displayAs: 'link' },
594+
});
561595
const compiled = fixture.nativeElement;
562596

563597
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -566,7 +600,9 @@ describe('ValueCellComponent', () => {
566600
});
567601

568602
it('should handle valid URL with port', () => {
569-
const { fixture } = makeComponent('https://example.com:8080');
603+
const { fixture } = makeComponent('https://example.com:8080', {
604+
uiSettings: { displayAs: 'link' },
605+
});
570606
const compiled = fixture.nativeElement;
571607

572608
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -575,7 +611,12 @@ describe('ValueCellComponent', () => {
575611
});
576612

577613
it('should handle valid URL with path', () => {
578-
const { fixture } = makeComponent('https://example.com/path/to/resource');
614+
const { fixture } = makeComponent(
615+
'https://example.com/path/to/resource',
616+
{
617+
uiSettings: { displayAs: 'link' },
618+
},
619+
);
579620
const compiled = fixture.nativeElement;
580621

581622
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -588,6 +629,9 @@ describe('ValueCellComponent', () => {
588629
it('should handle valid URL with query parameters', () => {
589630
const { fixture } = makeComponent(
590631
'https://example.com?param=value&other=test',
632+
{
633+
uiSettings: { displayAs: 'link' },
634+
},
591635
);
592636
const compiled = fixture.nativeElement;
593637

@@ -599,7 +643,9 @@ describe('ValueCellComponent', () => {
599643
});
600644

601645
it('should handle valid URL with fragment', () => {
602-
const { fixture } = makeComponent('https://example.com#section');
646+
const { fixture } = makeComponent('https://example.com#section', {
647+
uiSettings: { displayAs: 'link' },
648+
});
603649
const compiled = fixture.nativeElement;
604650

605651
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -624,15 +670,19 @@ describe('ValueCellComponent', () => {
624670
});
625671

626672
it('should treat "mailto:[email protected]" as valid URL for link component', () => {
627-
const { fixture } = makeComponent('mailto:[email protected]');
673+
const { fixture } = makeComponent('mailto:[email protected]', {
674+
uiSettings: { displayAs: 'link' },
675+
});
628676
const compiled = fixture.nativeElement;
629677

630678
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
631679
expect(component.isUrlValue()).toBe(true);
632680
});
633681

634682
it('should treat "tel:+1234567890" as valid URL for link component', () => {
635-
const { fixture } = makeComponent('tel:+1234567890');
683+
const { fixture } = makeComponent('tel:+1234567890', {
684+
uiSettings: { displayAs: 'link' },
685+
});
636686
const compiled = fixture.nativeElement;
637687

638688
expect(compiled.querySelector('wc-link-value')).toBeTruthy();
@@ -716,7 +766,9 @@ describe('ValueCellComponent', () => {
716766

717767
describe('complex scenarios', () => {
718768
it('should prioritize boolean over URL when both are valid', () => {
719-
const { fixture } = makeComponent('true');
769+
const { fixture } = makeComponent('true', {
770+
uiSettings: { displayAs: 'boolIcon' },
771+
});
720772
const compiled = fixture.nativeElement;
721773

722774
expect(compiled.querySelector('wc-boolean-value')).toBeTruthy();

0 commit comments

Comments
 (0)