Skip to content

Commit cf5a471

Browse files
authored
Merge pull request #75 from platform-mesh/resource-creation-status-check
Resource creation status check
2 parents 1be83d0 + e2cc357 commit cf5a471

File tree

6 files changed

+179
-115
lines changed

6 files changed

+179
-115
lines changed

projects/lib/services/resource/resource.service.spec.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -392,36 +392,6 @@ describe('ResourceService', () => {
392392
});
393393
});
394394

395-
describe('readOrganizations', () => {
396-
it('should read organizations', (done) => {
397-
mockApollo.query.mockReturnValue(of({ data: { orgList: [{ id: 1 }] } }));
398-
service
399-
.readOrganizations('orgList', ['id'], namespacedNodeContext)
400-
.subscribe((res) => {
401-
expect(res).toEqual([{ id: 1 }]);
402-
done();
403-
});
404-
});
405-
406-
it('should handle read organizations error', (done) => {
407-
const error = new Error('fail');
408-
mockApollo.query.mockReturnValue(throwError(() => error));
409-
console.error = jest.fn();
410-
411-
service
412-
.readOrganizations('orgList', ['id'], namespacedNodeContext)
413-
.subscribe({
414-
error: (err) => {
415-
expect(console.error).toHaveBeenCalledWith(
416-
'Error executing GraphQL query.',
417-
error,
418-
);
419-
done();
420-
},
421-
});
422-
});
423-
});
424-
425395
describe('delete', () => {
426396
it('should delete resource', (done) => {
427397
mockApollo.mutate.mockReturnValue(of({}));

projects/lib/services/resource/resource.service.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -161,34 +161,6 @@ export class ResourceService {
161161
});
162162
}
163163

164-
readOrganizations(
165-
operation: string,
166-
fields: any[],
167-
nodeContext: ResourceNodeContext,
168-
): Observable<any[]> {
169-
const query = gqlBuilder.query({
170-
operation: operation,
171-
fields,
172-
variables: {},
173-
});
174-
175-
return this.apolloFactory
176-
.apollo(nodeContext)
177-
.query({
178-
query: gql`
179-
${query.query}
180-
`,
181-
})
182-
.pipe(
183-
map((res: any) => res.data?.[operation]),
184-
catchError((error) => {
185-
this.alertErrors(error);
186-
console.error('Error executing GraphQL query.', error);
187-
return error;
188-
}),
189-
);
190-
}
191-
192164
delete(
193165
resource: Resource,
194166
resourceDefinition: ResourceDefinition,

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

Lines changed: 33 additions & 11 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 newOrganization = organizationToSwitch();
1213
<ui5-select
1314
id="select-switch"
14-
[value]="organizationToSwitch()"
15-
(input)="setOrganizationToSwitch($event)"
16-
(change)="setOrganizationToSwitch($event)"
15+
[value]="newOrganization?.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>{{newOrganization?.name}}</span>
35+
@if(newOrganization && !newOrganization?.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]="newOrganization?.ready ? undefined : texts.switchOrganization.tooltip"
43+
[disabled]="!newOrganization || !newOrganization?.ready"
44+
design="Emphasized"
45+
(ui5Click)="switchOrganization()"
46+
>{{
2547
texts.switchOrganization.button
2648
}}</ui5-button>
2749
</div>
@@ -36,14 +58,14 @@
3658
<ui5-input
3759
id="input-onboard"
3860
placeholder="{{ texts.onboardOrganization.placeholder }}"
39-
[formControl]="newOrganization"
40-
[valueState]="getValueState(newOrganization)"
61+
[formControl]="newOrganizationControl"
62+
[valueState]="getValueState(newOrganizationControl)"
4163
>
4264
<div slot="valueStateMessage">
4365
<a href="{{ k8sMessages.RFC_1035.href }}" target="_blank">{{ k8sMessages.RFC_1035.message }}</a>
4466
</div>
4567
</ui5-input>
46-
<ui5-button [disabled]="newOrganization.invalid" design="Emphasized" (ui5Click)="onboardOrganization()">{{
68+
<ui5-button [disabled]="newOrganizationControl.invalid" design="Emphasized" (ui5Click)="onboardOrganization()">{{
4769
texts.onboardOrganization.button
4870
}}</ui5-button>
4971
</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: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { OrganizationManagementComponent } from './organization-management.component';
21
import {
32
CUSTOM_ELEMENTS_SCHEMA,
43
NO_ERRORS_SCHEMA,
@@ -17,6 +16,7 @@ import {
1716
} from '@openmfp/portal-ui-lib';
1817
import { ResourceService } from '@platform-mesh/portal-ui-lib/services';
1918
import { of, throwError } from 'rxjs';
19+
import { OrganizationManagementComponent } from './organization-management.component';
2020

2121
describe('OrganizationManagementComponent', () => {
2222
let component: OrganizationManagementComponent;
@@ -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', () => {
@@ -134,23 +143,25 @@ describe('OrganizationManagementComponent', () => {
134143
reset: jest.fn(),
135144
};
136145
resourceServiceMock.create.mockReturnValue(of(mockResponse));
137-
component.newOrganization.setValue('newOrg');
138-
component.organizations.set(['existingOrg']);
146+
component.newOrganizationControl.setValue('newOrg');
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');
145-
expect(component.newOrganization.value).toBe('');
152+
expect(component.organizationToSwitch()).toEqual({
153+
name: 'newOrg',
154+
ready: false,
155+
});
156+
expect(component.newOrganizationControl.value).toBe('');
146157
expect(luigiClientMock.uxManager().showAlert).toHaveBeenCalled();
147158
});
148159

149160
it('should handle organization creation error', () => {
150161
resourceServiceMock.create.mockReturnValue(
151162
throwError(() => new Error('Creation failed')),
152163
);
153-
component.newOrganization.setValue('newOrg');
164+
component.newOrganizationControl.setValue('newOrg');
154165

155166
component.onboardOrganization();
156167

@@ -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,
@@ -282,4 +296,51 @@ describe('OrganizationManagementComponent', () => {
282296
const result = component.getValueState(formControl);
283297
expect(result).toBe('None');
284298
});
299+
300+
it('should handle error when reading organizations', () => {
301+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
302+
const mockError = new Error('Failed to fetch organizations');
303+
304+
resourceServiceMock.list.mockReturnValue(throwError(() => mockError));
305+
306+
component.readOrganizations();
307+
308+
expect(consoleSpy).toHaveBeenCalledWith(
309+
'Error reading organizations',
310+
mockError,
311+
);
312+
313+
consoleSpy.mockRestore();
314+
});
315+
316+
it('should update existing organizationToSwitch when reading organizations', () => {
317+
const mockOrganizations = [
318+
{
319+
metadata: { name: 'org1' },
320+
status: { conditions: [{ type: 'Ready', status: 'True' }] },
321+
},
322+
{
323+
metadata: { name: 'org2' },
324+
status: { conditions: [{ type: 'Ready', status: 'True' }] },
325+
},
326+
];
327+
328+
// Set an existing organization to switch
329+
component.organizationToSwitch.set({ name: 'org2', ready: false });
330+
331+
resourceServiceMock.list.mockReturnValue(of(mockOrganizations as any));
332+
333+
component.readOrganizations();
334+
335+
expect(resourceServiceMock.list).toHaveBeenCalled();
336+
expect(component.organizations()).toEqual([
337+
{ name: 'org1', ready: true },
338+
{ name: 'org2', ready: true },
339+
]);
340+
// Should find and update the existing organizationToSwitch
341+
expect(component.organizationToSwitch()).toEqual({
342+
name: 'org2',
343+
ready: true,
344+
});
345+
});
285346
});

0 commit comments

Comments
 (0)