Skip to content

Commit 2e52f74

Browse files
committed
feat: add resource creation status icon
1 parent 1be83d0 commit 2e52f74

File tree

4 files changed

+122
-47
lines changed

4 files changed

+122
-47
lines changed

projects/wc/src/app/components/organization-management/organization-management.component.html

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,41 @@
99
}}</ui5-label
1010
><br />
1111
<div class="organization-management-input">
12+
@let organizationToSwitchObj = organizationToSwitch();
1213
<ui5-select
1314
id="select-switch"
14-
[value]="organizationToSwitch()"
15-
(input)="setOrganizationToSwitch($event)"
16-
(change)="setOrganizationToSwitch($event)"
15+
[value]="organizationToSwitchObj?.name"
16+
(ui5Change)="setOrganizationToSwitch($event)"
1717
>
18-
@for (org of organizations(); track org) {
19-
<ui5-option [value]="org" [selected]="org === organizationToSwitch()">{{
20-
org
21-
}}</ui5-option>
18+
@for (org of organizations(); track org.name) {
19+
<ui5-option
20+
class="option"
21+
[tooltip]="org.ready ? undefined : texts.switchOrganization.tooltip"
22+
[value]="org.name"
23+
>
24+
<div class="option">
25+
<span>{{org.name}}</span>
26+
@if(!org.ready) {
27+
<ui5-icon name="alert" design="Critical"></ui5-icon>
28+
}
29+
</div>
30+
</ui5-option>
2231
}
32+
33+
<div slot="label" class="option">
34+
<span>{{organizationToSwitchObj?.name}}</span>
35+
@if(organizationToSwitchObj && !organizationToSwitchObj?.ready) {
36+
<ui5-icon name="alert" design="Critical"></ui5-icon>
37+
}
38+
</div>
39+
2340
</ui5-select>
24-
<ui5-button [disabled]="!organizationToSwitch()" design="Emphasized" (ui5Click)="switchOrganization()">{{
41+
<ui5-button
42+
[tooltip]="organizationToSwitchObj?.ready ? undefined : texts.switchOrganization.tooltip"
43+
[disabled]="!organizationToSwitchObj || !organizationToSwitchObj?.ready"
44+
design="Emphasized"
45+
(ui5Click)="switchOrganization()"
46+
>{{
2547
texts.switchOrganization.button
2648
}}</ui5-button>
2749
</div>

projects/wc/src/app/components/organization-management/organization-management.component.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@
1111
gap: 1rem;
1212
}
1313

14+
.option {
15+
display: flex;
16+
align-items: center;
17+
gap: 0.5rem;
18+
}

projects/wc/src/app/components/organization-management/organization-management.component.spec.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('OrganizationManagementComponent', () => {
2828

2929
beforeEach(async () => {
3030
resourceServiceMock = {
31-
readOrganizations: jest.fn(),
31+
list: jest.fn(),
3232
create: jest.fn(),
3333
} as any;
3434

@@ -75,7 +75,7 @@ describe('OrganizationManagementComponent', () => {
7575
translationTable: { hello: 'world' },
7676
} as any as NodeContext;
7777

78-
resourceServiceMock.readOrganizations.mockReturnValue(of({} as any));
78+
resourceServiceMock.list.mockReturnValue(of([] as any));
7979

8080
const contextSignal = signal<NodeContext | null>(mockContext);
8181
component.context = contextSignal as any;
@@ -92,12 +92,16 @@ describe('OrganizationManagementComponent', () => {
9292
});
9393

9494
it('should read organizations on init', () => {
95-
const mockOrganizations = {
96-
Accounts: [
97-
{ metadata: { name: 'org1' } },
98-
{ metadata: { name: 'org2' } },
99-
],
100-
};
95+
const mockOrganizations = [
96+
{
97+
metadata: { name: 'org1' },
98+
status: { conditions: [{ type: 'Ready', status: 'True' }] },
99+
},
100+
{
101+
metadata: { name: 'org2' },
102+
status: { conditions: [{ type: 'Ready', status: 'False' }] },
103+
},
104+
];
101105
const mockGlobalContext: LuigiGlobalContext = {
102106
portalContext: {},
103107
userId: 'user1',
@@ -108,20 +112,25 @@ describe('OrganizationManagementComponent', () => {
108112
};
109113

110114
component.context = (() => mockGlobalContext) as any;
111-
resourceServiceMock.readOrganizations.mockReturnValue(
112-
of(mockOrganizations as any),
113-
);
115+
resourceServiceMock.list.mockReturnValue(of(mockOrganizations as any));
114116

115117
component.ngOnInit();
116118

117-
expect(resourceServiceMock.readOrganizations).toHaveBeenCalled();
118-
expect(component.organizations()).toEqual(['org1', 'org2']);
119+
expect(resourceServiceMock.list).toHaveBeenCalled();
120+
expect(component.organizations()).toEqual([
121+
{ name: 'org1', ready: true },
122+
{ name: 'org2', ready: false },
123+
]);
119124
});
120125

121126
it('should set organization to switch', () => {
122-
const event = { target: { value: 'testOrg' } };
127+
component.organizations.set([{ name: 'testOrg', ready: false }]);
128+
const event = { selectedOption: { _state: { value: 'testOrg' } } };
123129
component.setOrganizationToSwitch(event);
124-
expect(component.organizationToSwitch()).toBe('testOrg');
130+
expect(component.organizationToSwitch()).toEqual({
131+
name: 'testOrg',
132+
ready: false,
133+
});
125134
});
126135

127136
it('should onboard new organization successfully', () => {
@@ -135,13 +144,15 @@ describe('OrganizationManagementComponent', () => {
135144
};
136145
resourceServiceMock.create.mockReturnValue(of(mockResponse));
137146
component.newOrganization.setValue('newOrg');
138-
component.organizations.set(['existingOrg']);
147+
component.organizations.set([{ name: 'existingOrg', ready: false }]);
139148

140149
component.onboardOrganization();
141150

142151
expect(resourceServiceMock.create).toHaveBeenCalled();
143-
expect(component.organizations()).toEqual(['newOrg', 'existingOrg']);
144-
expect(component.organizationToSwitch()).toBe('newOrg');
152+
expect(component.organizationToSwitch()).toEqual({
153+
name: 'newOrg',
154+
ready: false,
155+
});
145156
expect(component.newOrganization.value).toBe('');
146157
expect(luigiClientMock.uxManager().showAlert).toHaveBeenCalled();
147158
});
@@ -177,7 +188,7 @@ describe('OrganizationManagementComponent', () => {
177188
uiOptions: [],
178189
};
179190
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
180-
component.organizationToSwitch.set('newOrg');
191+
component.organizationToSwitch.set({ name: 'newOrg', ready: false });
181192
Object.defineProperty(window, 'location', {
182193
value: { protocol: 'https:', port: '8080' },
183194
writable: true,
@@ -205,7 +216,10 @@ describe('OrganizationManagementComponent', () => {
205216
uiOptions: [],
206217
};
207218
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
208-
component.organizationToSwitch.set('invalid-org-name-'); // Invalid: ends with hyphen
219+
component.organizationToSwitch.set({
220+
name: 'invalid-org-name-',
221+
ready: false,
222+
}); // Invalid: ends with hyphen
209223

210224
await component.switchOrganization();
211225

@@ -232,7 +246,7 @@ describe('OrganizationManagementComponent', () => {
232246
uiOptions: [],
233247
};
234248
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
235-
component.organizationToSwitch.set('validorg');
249+
component.organizationToSwitch.set({ name: 'validorg', ready: false });
236250
Object.defineProperty(window, 'location', {
237251
value: { protocol: 'https:', port: '' },
238252
writable: true,

projects/wc/src/app/components/organization-management/organization-management.component.ts

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { k8sMessages } from '../../consts/k8s-messages';
2-
import { k8sNameValidator } from '../../validators/k8s-name-validator';
31
import {
42
ChangeDetectionStrategy,
53
Component,
4+
DestroyRef,
65
OnInit,
76
ViewEncapsulation,
87
effect,
98
inject,
109
input,
11-
linkedSignal,
1210
signal,
1311
} from '@angular/core';
12+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
1413
import {
1514
FormControl,
1615
FormsModule,
@@ -34,11 +33,14 @@ import {
3433
} from '@platform-mesh/portal-ui-lib/utils';
3534
import {
3635
ButtonComponent,
36+
IconComponent,
3737
InputComponent,
3838
LabelComponent,
3939
OptionComponent,
4040
SelectComponent,
4141
} from '@ui5/webcomponents-ngx';
42+
import { k8sMessages } from '../../consts/k8s-messages';
43+
import { k8sNameValidator } from '../../validators/k8s-name-validator';
4244

4345
@Component({
4446
selector: 'organization-management',
@@ -51,6 +53,7 @@ import {
5153
SelectComponent,
5254
FormsModule,
5355
ReactiveFormsModule,
56+
IconComponent,
5457
],
5558
templateUrl: './organization-management.component.html',
5659
styleUrl: './organization-management.component.scss',
@@ -61,13 +64,14 @@ export class OrganizationManagementComponent implements OnInit {
6164
private i18nService = inject(I18nService);
6265
private resourceService = inject(ResourceService);
6366
private envConfigService = inject(EnvConfigService);
67+
private destroyRef = inject(DestroyRef);
6468

6569
context = input<ResourceNodeContext>();
6670
LuigiClient = input<LuigiClient>();
6771

6872
texts: any = {};
69-
organizations = signal<string[]>([]);
70-
organizationToSwitch = linkedSignal(() => this.organizations()[0] ?? '');
73+
organizations = signal<{ name: string; ready: boolean }[]>([]);
74+
organizationToSwitch = signal<{ name: string; ready: boolean } | null>(null);
7175
newOrganization = new FormControl('', {
7276
validators: [Validators.required, k8sNameValidator],
7377
nonNullable: true,
@@ -90,24 +94,52 @@ export class OrganizationManagementComponent implements OnInit {
9094
}
9195

9296
setOrganizationToSwitch($event: any) {
93-
this.organizationToSwitch.set($event.target.value);
97+
this.organizationToSwitch.set(
98+
this.organizations().find(
99+
(o) => o.name === $event.selectedOption._state.value,
100+
) ?? null,
101+
);
94102
}
95103

96104
readOrganizations() {
97105
const fields = generateGraphQLFields([
98106
{
99-
property: 'Accounts.metadata.name',
107+
property: [
108+
'metadata.name',
109+
'status.conditions.status',
110+
'status.conditions.type',
111+
],
100112
},
101113
]);
102-
const queryOperation = 'core_platform_mesh_io';
103-
114+
const queryOperation = 'core_platform_mesh_io_accounts';
104115
this.resourceService
105-
.readOrganizations(queryOperation, fields, this.context())
116+
.list(queryOperation, fields, this.context())
117+
.pipe(takeUntilDestroyed(this.destroyRef))
106118
.subscribe({
107119
next: (result) => {
108120
this.organizations.set(
109-
result['Accounts'].map((o) => o.metadata.name),
121+
result.map((o) => ({
122+
name: o.metadata.name,
123+
ready:
124+
o.status.conditions.find((c) => c.type === 'Ready')?.status ===
125+
'True',
126+
})),
110127
);
128+
129+
const organizationToSwitch = this.organizationToSwitch();
130+
131+
if (!organizationToSwitch) {
132+
this.organizationToSwitch.set(this.organizations()[0]);
133+
} else {
134+
this.organizationToSwitch.set(
135+
this.organizations().find(
136+
(o) => o.name === organizationToSwitch.name,
137+
) ?? null,
138+
);
139+
}
140+
},
141+
error: (error) => {
142+
console.error('Error reading organizations', error);
111143
},
112144
});
113145
}
@@ -130,17 +162,16 @@ export class OrganizationManagementComponent implements OnInit {
130162
.subscribe({
131163
next: (result) => {
132164
console.debug('Resource created', result);
133-
this.organizations.set([
134-
this.newOrganization.value,
135-
...this.organizations(),
136-
]);
137-
this.organizationToSwitch.set(this.newOrganization.value);
165+
this.organizationToSwitch.set({
166+
name: this.newOrganization.value,
167+
ready: false,
168+
});
138169
this.newOrganization.reset();
139170
this.LuigiClient()
140171
.uxManager()
141172
.showAlert({
142173
text: this.getMessageForOrganizationCreation(
143-
this.organizationToSwitch(),
174+
this.organizationToSwitch().name,
144175
),
145176
type: 'info',
146177
});
@@ -176,6 +207,9 @@ export class OrganizationManagementComponent implements OnInit {
176207
button: this.i18nService.getTranslation(
177208
'ORGANIZATION_MANAGEMENT_SWITCH_BUTTON',
178209
),
210+
tooltip: this.i18nService.getTranslation(
211+
'ORGANIZATION_MANAGEMENT_NOT_READY_TOOLTIP',
212+
),
179213
},
180214

181215
onboardOrganization: {
@@ -210,7 +244,7 @@ export class OrganizationManagementComponent implements OnInit {
210244
const { baseDomain } = await this.envConfigService.getEnvConfig();
211245
const protocol = window.location.protocol;
212246
const sanitizedOrg = this.sanitizeSubdomainInput(
213-
this.organizationToSwitch(),
247+
this.organizationToSwitch().name,
214248
);
215249

216250
if (!sanitizedOrg) {

0 commit comments

Comments
 (0)