Skip to content

Commit 781d8d5

Browse files
committed
feat: change fieldDefinition interface
1 parent f6af56d commit 781d8d5

File tree

5 files changed

+112
-62
lines changed

5 files changed

+112
-62
lines changed

docs/readme-generic-ui.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,18 @@ Each field definition supports the following properties:
6868
- `"label"`: Display name for the group
6969
- `"delimiter"`: String used to separate grouped values
7070
- `"multiline"`: Boolean flag for multiline display of grouped values (default: true) When true, values are displayed on separate lines
71-
- `"labelDisplay"`: Boolean value for using the defaults or an object for customizing the visual appearance of field values:
72-
- `"backgroundColor"`: Background color for the value (CSS color value)
73-
- `"color"`: Text color for the value (CSS color value)
74-
- `"fontWeight"`: Font weight for the value (CSS font-weight value)
75-
- `"fontStyle"`: Font style for the value (CSS font-style value)
76-
- `"textDecoration"`: Text decoration for the value (CSS text-decoration value)
77-
- `"textTransform"`: Text transformation for the value (CSS text-transform value)
78-
- `"displayAsPlainText"`: Boolean valu that give you ability to render value as it is, without any built-in transformation.
71+
- `"uiSettings"`: Object for configuring UI-specific display settings:
72+
- `"labelDisplay"`: Boolean value for using the defaults or an object for customizing the visual appearance of field values:
73+
- `"backgroundColor"`: Background color for the value (CSS color value)
74+
- `"color"`: Text color for the value (CSS color value)
75+
- `"fontWeight"`: Font weight for the value (CSS font-weight value)
76+
- `"fontStyle"`: Font style for the value (CSS font-style value)
77+
- `"textDecoration"`: Text decoration for the value (CSS text-decoration value)
78+
- `"textTransform"`: Text transformation for the value (CSS text-transform value)
79+
- `"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
82+
- `"withCopyButton"`: Boolean flag to show a copy button next to the value for easy copying to clipboard
7983
- `"dynamicValuesDefinition"`: Configuration for dynamic value loading:
8084
- `"operation"`: GraphQL operation name
8185
- `"gqlQuery"`: GraphQL query string
@@ -85,6 +89,13 @@ Each field definition supports the following properties:
8589
#### Example Content Configuration for an Accounts Node
8690
Below is an example content-configuration for an accounts node using the generic list view.
8791

92+
This example demonstrates various features including:
93+
- **Secret fields**: The "Key" field in `listView` and "API Key" field in `detailView` use `displayAs: "secret"` to hide sensitive data with a toggle
94+
- **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+
- **Custom styling**: The "Type" and "Display Name" fields use `labelDisplay` for visual customization
97+
- **Field grouping**: Contact information is grouped using the `group` property
98+
8899
```json
89100
{
90101
"name": "accounts",
@@ -134,6 +145,10 @@ Below is an example content-configuration for an accounts node using the generic
134145
"propertyField": {
135146
"key": "OPENAI_API_KEY",
136147
"transform": ["uppercase", "encode"]
148+
},
149+
"uiSettings": {
150+
"displayAs": "secret",
151+
"withCopyButton": true
137152
}
138153
},
139154
{
@@ -180,6 +195,29 @@ Below is an example content-configuration for an accounts node using the generic
180195
"fontWeight": "600"
181196
}
182197
},
198+
{
199+
"label": "API Key",
200+
"property": "spec.credentials.apiKey",
201+
"uiSettings": {
202+
"displayAs": "secret",
203+
"withCopyButton": true
204+
}
205+
},
206+
{
207+
"label": "Account ID",
208+
"property": "metadata.uid",
209+
"uiSettings": {
210+
"withCopyButton": true
211+
}
212+
},
213+
{
214+
"label": "External URL",
215+
"property": "spec.externalUrl",
216+
"uiSettings": {
217+
"displayAs": "plainText",
218+
"withCopyButton": true
219+
}
220+
},
183221
{
184222
"label": "Contact Info",
185223
"property": "spec.email",

projects/lib/models/models/resource.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export interface PropertyField {
2121
transform?: TransformType[];
2222
}
2323

24+
export interface UiSettings {
25+
labelDisplay?: LabelDisplay | boolean;
26+
displayAs?: 'plainText' | 'secret';
27+
withCopyButton?: boolean;
28+
}
29+
2430
export interface FieldDefinition {
2531
label?: string;
2632
property: string | string[];
@@ -34,10 +40,7 @@ export interface FieldDefinition {
3440
delimiter?: string;
3541
multiline?: boolean;
3642
};
37-
labelDisplay?: LabelDisplay | boolean;
38-
displayAsPlainText?: boolean;
39-
withCopyButton?: boolean;
40-
displayAsSecret?: boolean;
43+
uiSettings?: UiSettings;
4144
dynamicValuesDefinition?: {
4245
operation: string;
4346
gqlQuery: string;

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
@if (displayAsPlainText()) {
1+
@if (displayAs() === 'plainText') {
22
{{ value() }}
3-
} @else if (displayAsSecret()) {
3+
} @else if (displayAs() === 'secret') {
44
<wc-secret-value [value]="value()"></wc-secret-value>
5+
} @else if (isBoolLike()) {
6+
<wc-boolean-value [boolValue]="boolValue()!"></wc-boolean-value>
7+
} @else if (isUrlValue()) {
8+
<wc-link-value [urlValue]="stringValue()!"></wc-link-value>
9+
} @else if (isLabelValue()) {
10+
<wc-label-value [labelDisplay]="labelDisplayValue()!" [value]="value()"></wc-label-value>
511
} @else {
6-
@if (isBoolLike()) {
7-
<wc-boolean-value [boolValue]="boolValue()!"></wc-boolean-value>
8-
} @else if (isUrlValue()) {
9-
<wc-link-value [urlValue]="stringValue()!"></wc-link-value>
10-
} @else if (isLabelValue()) {
11-
<wc-label-value [labelDisplay]="labelDisplayValue()!" [value]="value()"></wc-label-value>
12-
} @else {
13-
{{ value() }}
14-
}
12+
{{ value() }}
1513
}
1614

1715
@if (withCopyButton()) {

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

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { ValueCellComponent } from './value-cell.component';
22
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
33
import { ComponentFixture, TestBed } from '@angular/core/testing';
44
import { LuigiClient } from '@luigi-project/client/luigi-element';
5-
import { FieldDefinition, Resource } from '@platform-mesh/portal-ui-lib/models/models';
6-
5+
import {
6+
FieldDefinition,
7+
Resource,
8+
} from '@platform-mesh/portal-ui-lib/models/models';
79

810
describe('ValueCellComponent', () => {
911
let component: ValueCellComponent;
@@ -164,7 +166,9 @@ describe('ValueCellComponent', () => {
164166
describe('labelDisplay functionality', () => {
165167
it('should render label-value component when labelDisplay is an object', () => {
166168
const labelDisplay = { backgroundColor: '#ffffff', color: '#000000' };
167-
const { fixture } = makeComponent('test-value', { labelDisplay });
169+
const { fixture } = makeComponent('test-value', {
170+
uiSettings: { labelDisplay },
171+
});
168172
const compiled = fixture.nativeElement;
169173

170174
expect(compiled.querySelector('wc-label-value')).toBeTruthy();
@@ -173,7 +177,9 @@ describe('ValueCellComponent', () => {
173177
});
174178

175179
it('should render label-value component when labelDisplay is true', () => {
176-
const { fixture } = makeComponent('test-value', { labelDisplay: true });
180+
const { fixture } = makeComponent('test-value', {
181+
uiSettings: { labelDisplay: true },
182+
});
177183
const compiled = fixture.nativeElement;
178184

179185
expect(compiled.querySelector('wc-label-value')).toBeTruthy();
@@ -182,7 +188,9 @@ describe('ValueCellComponent', () => {
182188
});
183189

184190
it('should not render label-value component when labelDisplay is false', () => {
185-
const { fixture } = makeComponent('test-value', { labelDisplay: false });
191+
const { fixture } = makeComponent('test-value', {
192+
uiSettings: { labelDisplay: false },
193+
});
186194
const compiled = fixture.nativeElement;
187195

188196
expect(compiled.querySelector('wc-label-value')).toBeFalsy();
@@ -192,7 +200,7 @@ describe('ValueCellComponent', () => {
192200

193201
it('should not render label-value component when labelDisplay is undefined', () => {
194202
const { fixture } = makeComponent('test-value', {
195-
labelDisplay: undefined,
203+
uiSettings: { labelDisplay: undefined },
196204
});
197205
const compiled = fixture.nativeElement;
198206

@@ -203,7 +211,7 @@ describe('ValueCellComponent', () => {
203211

204212
it('should not render label-value component when labelDisplay is null', () => {
205213
const { fixture } = makeComponent('test-value', {
206-
labelDisplay: null as any,
214+
uiSettings: { labelDisplay: null as any },
207215
});
208216
const compiled = fixture.nativeElement;
209217

@@ -214,7 +222,7 @@ describe('ValueCellComponent', () => {
214222

215223
it('should render label-value component when labelDisplay is a string', () => {
216224
const { fixture } = makeComponent('test-value', {
217-
labelDisplay: 'some-string' as any,
225+
uiSettings: { labelDisplay: 'some-string' as any },
218226
});
219227
const compiled = fixture.nativeElement;
220228

@@ -225,7 +233,7 @@ describe('ValueCellComponent', () => {
225233

226234
it('should render label-value component when labelDisplay is a number', () => {
227235
const { fixture } = makeComponent('test-value', {
228-
labelDisplay: 42 as any,
236+
uiSettings: { labelDisplay: 42 as any },
229237
});
230238
const compiled = fixture.nativeElement;
231239

@@ -235,39 +243,41 @@ describe('ValueCellComponent', () => {
235243
});
236244
});
237245

238-
describe('displayAsSecret functionality', () => {
239-
it('should render secret-value component when displayAsSecret is true', () => {
246+
describe('displayAs secret functionality', () => {
247+
it('should render secret-value component when displayAs is secret', () => {
240248
const { fixture } = makeComponent('secret-password', {
241-
displayAsSecret: true,
249+
uiSettings: { displayAs: 'secret' },
242250
});
243251
const compiled = fixture.nativeElement;
244252

245253
expect(compiled.querySelector('wc-secret-value')).toBeTruthy();
246-
expect(component.displayAsSecret()).toBe(true);
254+
expect(component.displayAs()).toBe('secret');
247255
});
248256

249-
it('should not render secret-value component when displayAsSecret is false', () => {
257+
it('should not render secret-value component when displayAs is not secret', () => {
250258
const { fixture } = makeComponent('plain-text', {
251-
displayAsSecret: false,
259+
uiSettings: { displayAs: 'plainText' },
252260
});
253261
const compiled = fixture.nativeElement;
254262

255263
expect(compiled.querySelector('wc-secret-value')).toBeFalsy();
256-
expect(component.displayAsSecret()).toBe(false);
264+
expect(component.displayAs()).toBe('plainText');
257265
});
258266

259-
it('should not render secret-value component when displayAsSecret is undefined', () => {
267+
it('should not render secret-value component when displayAs is undefined', () => {
260268
const { fixture } = makeComponent('plain-text');
261269
const compiled = fixture.nativeElement;
262270

263271
expect(compiled.querySelector('wc-secret-value')).toBeFalsy();
264-
expect(component.displayAsSecret()).toBeUndefined();
272+
expect(component.displayAs()).toBeUndefined();
265273
});
266274
});
267275

268276
describe('withCopyButton functionality', () => {
269277
it('should render copy button when withCopyButton is true', () => {
270-
const { fixture } = makeComponent('test-value', { withCopyButton: true });
278+
const { fixture } = makeComponent('test-value', {
279+
uiSettings: { withCopyButton: true },
280+
});
271281
const compiled = fixture.nativeElement;
272282

273283
expect(compiled.querySelector('ui5-icon[name="copy"]')).toBeTruthy();
@@ -276,7 +286,7 @@ describe('ValueCellComponent', () => {
276286

277287
it('should not render copy button when withCopyButton is false', () => {
278288
const { fixture } = makeComponent('test-value', {
279-
withCopyButton: false,
289+
uiSettings: { withCopyButton: false },
280290
});
281291
const compiled = fixture.nativeElement;
282292

@@ -300,7 +310,7 @@ describe('ValueCellComponent', () => {
300310
const customLuigiClient = createMockLuigiClient(showAlertSpy);
301311
const { fixture } = makeComponent(
302312
'test-value',
303-
{ withCopyButton: true },
313+
{ uiSettings: { withCopyButton: true } },
304314
customLuigiClient,
305315
);
306316

@@ -320,7 +330,9 @@ describe('ValueCellComponent', () => {
320330
});
321331

322332
it('should stop event propagation when copy button is clicked', () => {
323-
const { fixture } = makeComponent('test-value', { withCopyButton: true });
333+
const { fixture } = makeComponent('test-value', {
334+
uiSettings: { withCopyButton: true },
335+
});
324336
const compiled = fixture.nativeElement;
325337
const copyButton = compiled.querySelector('ui5-icon[name="copy"]');
326338

@@ -334,10 +346,10 @@ describe('ValueCellComponent', () => {
334346
});
335347
});
336348

337-
describe('displayAsPlainText functionality', () => {
338-
it('should render plain text when displayAsPlainText is true', () => {
349+
describe('displayAs plainText functionality', () => {
350+
it('should render plain text when displayAs is plainText', () => {
339351
const { fixture } = makeComponent('test-value', {
340-
displayAsPlainText: true,
352+
uiSettings: { displayAs: 'plainText' },
341353
});
342354
const compiled = fixture.nativeElement;
343355

@@ -348,9 +360,9 @@ describe('ValueCellComponent', () => {
348360
expect(compiled.textContent.trim()).toContain('test-value');
349361
});
350362

351-
it('should not render plain text when displayAsPlainText is false', () => {
363+
it('should not render plain text when displayAs is not plainText', () => {
352364
const { fixture } = makeComponent('https://example.com', {
353-
displayAsPlainText: false,
365+
uiSettings: {},
354366
});
355367
const compiled = fixture.nativeElement;
356368

@@ -650,7 +662,7 @@ describe('ValueCellComponent', () => {
650662

651663
it('should prioritize boolean over label when both are valid', () => {
652664
const { fixture } = makeComponent('true', {
653-
labelDisplay: { backgroundColor: '#ffffff' },
665+
uiSettings: { labelDisplay: { backgroundColor: '#ffffff' } },
654666
});
655667
const compiled = fixture.nativeElement;
656668

@@ -661,7 +673,7 @@ describe('ValueCellComponent', () => {
661673

662674
it('should prioritize URL over label when both are valid', () => {
663675
const { fixture } = makeComponent('https://example.com', {
664-
labelDisplay: { backgroundColor: '#ffffff' },
676+
uiSettings: { labelDisplay: { backgroundColor: '#ffffff' } },
665677
});
666678
const compiled = fixture.nativeElement;
667679

@@ -672,7 +684,7 @@ describe('ValueCellComponent', () => {
672684

673685
it('should render label when boolean and URL are not valid but labelDisplay is provided', () => {
674686
const { fixture } = makeComponent('some-text', {
675-
labelDisplay: { backgroundColor: '#ffffff' },
687+
uiSettings: { labelDisplay: { backgroundColor: '#ffffff' } },
676688
});
677689
const compiled = fixture.nativeElement;
678690

@@ -682,7 +694,9 @@ describe('ValueCellComponent', () => {
682694
});
683695

684696
it('should render plain text when no special rendering is needed', () => {
685-
const { fixture } = makeComponent('some-text', { labelDisplay: false });
697+
const { fixture } = makeComponent('some-text', {
698+
uiSettings: { labelDisplay: false },
699+
});
686700
const compiled = fixture.nativeElement;
687701

688702
expect(compiled.querySelector('wc-boolean-value')).toBeFalsy();
@@ -691,4 +705,4 @@ describe('ValueCellComponent', () => {
691705
expect(compiled.textContent.trim()).toBe('some-text');
692706
});
693707
});
694-
});
708+
});

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,9 @@ export class ValueCellComponent {
4040
value = computed(() =>
4141
getResourceValueByJsonPath(this.resource(), this.fieldDefinition()),
4242
);
43-
labelDisplay = computed(() => this.fieldDefinition().labelDisplay);
44-
displayAsSecret = computed(() => this.fieldDefinition().displayAsSecret);
45-
withCopyButton = computed(() => this.fieldDefinition().withCopyButton);
46-
displayAsPlainText = computed(
47-
() => this.fieldDefinition().displayAsPlainText,
48-
);
43+
uiSettings = computed(() => this.fieldDefinition().uiSettings);
44+
displayAs = computed(() => this.uiSettings()?.displayAs);
45+
withCopyButton = computed(() => this.uiSettings()?.withCopyButton);
4946

5047
isLabelValue = computed(() => this.labelDisplayValue() !== undefined);
5148
isBoolLike = computed(() => this.boolValue() !== undefined);
@@ -54,7 +51,7 @@ export class ValueCellComponent {
5451
boolValue = computed(() => this.normalizeBoolean(this.value()));
5552
stringValue = computed(() => this.normalizeString(this.value()));
5653
labelDisplayValue = computed(() =>
57-
this.normalizeLabelDisplay(this.labelDisplay()),
54+
this.normalizeLabelDisplay(this.uiSettings()?.labelDisplay),
5855
);
5956

6057
private normalizeBoolean(value: unknown): boolean | undefined {

0 commit comments

Comments
 (0)