Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions projects/lib/services/resource/resource.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,36 +392,6 @@ describe('ResourceService', () => {
});
});

describe('readOrganizations', () => {
it('should read organizations', (done) => {
mockApollo.query.mockReturnValue(of({ data: { orgList: [{ id: 1 }] } }));
service
.readOrganizations('orgList', ['id'], namespacedNodeContext)
.subscribe((res) => {
expect(res).toEqual([{ id: 1 }]);
done();
});
});

it('should handle read organizations error', (done) => {
const error = new Error('fail');
mockApollo.query.mockReturnValue(throwError(() => error));
console.error = jest.fn();

service
.readOrganizations('orgList', ['id'], namespacedNodeContext)
.subscribe({
error: (err) => {
expect(console.error).toHaveBeenCalledWith(
'Error executing GraphQL query.',
error,
);
done();
},
});
});
});

describe('delete', () => {
it('should delete resource', (done) => {
mockApollo.mutate.mockReturnValue(of({}));
Expand Down
28 changes: 0 additions & 28 deletions projects/lib/services/resource/resource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,34 +161,6 @@ export class ResourceService {
});
}

readOrganizations(
operation: string,
fields: any[],
nodeContext: ResourceNodeContext,
): Observable<any[]> {
const query = gqlBuilder.query({
operation: operation,
fields,
variables: {},
});

return this.apolloFactory
.apollo(nodeContext)
.query({
query: gql`
${query.query}
`,
})
.pipe(
map((res: any) => res.data?.[operation]),
catchError((error) => {
this.alertErrors(error);
console.error('Error executing GraphQL query.', error);
return error;
}),
);
}

delete(
resource: Resource,
resourceDefinition: ResourceDefinition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,41 @@
}}</ui5-label
><br />
<div class="organization-management-input">
@let newOrganization = organizationToSwitch();
<ui5-select
id="select-switch"
[value]="organizationToSwitch()"
(input)="setOrganizationToSwitch($event)"
(change)="setOrganizationToSwitch($event)"
[value]="newOrganization?.name"
(ui5Change)="setOrganizationToSwitch($event)"
>
@for (org of organizations(); track org) {
<ui5-option [value]="org" [selected]="org === organizationToSwitch()">{{
org
}}</ui5-option>
@for (org of organizations(); track org.name) {
<ui5-option
class="option"
[tooltip]="org.ready ? undefined : texts.switchOrganization.tooltip"
[value]="org.name"
>
<div class="option">
<span>{{org.name}}</span>
@if(!org.ready) {
<ui5-icon name="alert" design="Critical"></ui5-icon>
}
</div>
</ui5-option>
}

<div slot="label" class="option">
<span>{{newOrganization?.name}}</span>
@if(newOrganization && !newOrganization?.ready) {
<ui5-icon name="alert" design="Critical"></ui5-icon>
}
</div>

</ui5-select>
<ui5-button [disabled]="!organizationToSwitch()" design="Emphasized" (ui5Click)="switchOrganization()">{{
<ui5-button
[tooltip]="newOrganization?.ready ? undefined : texts.switchOrganization.tooltip"
[disabled]="!newOrganization || !newOrganization?.ready"
design="Emphasized"
(ui5Click)="switchOrganization()"
>{{
texts.switchOrganization.button
}}</ui5-button>
</div>
Expand All @@ -36,14 +58,14 @@
<ui5-input
id="input-onboard"
placeholder="{{ texts.onboardOrganization.placeholder }}"
[formControl]="newOrganization"
[valueState]="getValueState(newOrganization)"
[formControl]="newOrganizationControl"
[valueState]="getValueState(newOrganizationControl)"
>
<div slot="valueStateMessage">
<a href="{{ k8sMessages.RFC_1035.href }}" target="_blank">{{ k8sMessages.RFC_1035.message }}</a>
</div>
</ui5-input>
<ui5-button [disabled]="newOrganization.invalid" design="Emphasized" (ui5Click)="onboardOrganization()">{{
<ui5-button [disabled]="newOrganizationControl.invalid" design="Emphasized" (ui5Click)="onboardOrganization()">{{
texts.onboardOrganization.button
}}</ui5-button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
gap: 1rem;
}

.option {
display: flex;
align-items: center;
gap: 0.5rem;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { OrganizationManagementComponent } from './organization-management.component';
import {
CUSTOM_ELEMENTS_SCHEMA,
NO_ERRORS_SCHEMA,
Expand All @@ -17,6 +16,7 @@ import {
} from '@openmfp/portal-ui-lib';
import { ResourceService } from '@platform-mesh/portal-ui-lib/services';
import { of, throwError } from 'rxjs';
import { OrganizationManagementComponent } from './organization-management.component';

describe('OrganizationManagementComponent', () => {
let component: OrganizationManagementComponent;
Expand All @@ -28,7 +28,7 @@ describe('OrganizationManagementComponent', () => {

beforeEach(async () => {
resourceServiceMock = {
readOrganizations: jest.fn(),
list: jest.fn(),
create: jest.fn(),
} as any;

Expand Down Expand Up @@ -75,7 +75,7 @@ describe('OrganizationManagementComponent', () => {
translationTable: { hello: 'world' },
} as any as NodeContext;

resourceServiceMock.readOrganizations.mockReturnValue(of({} as any));
resourceServiceMock.list.mockReturnValue(of([] as any));

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

it('should read organizations on init', () => {
const mockOrganizations = {
Accounts: [
{ metadata: { name: 'org1' } },
{ metadata: { name: 'org2' } },
],
};
const mockOrganizations = [
{
metadata: { name: 'org1' },
status: { conditions: [{ type: 'Ready', status: 'True' }] },
},
{
metadata: { name: 'org2' },
status: { conditions: [{ type: 'Ready', status: 'False' }] },
},
];
const mockGlobalContext: LuigiGlobalContext = {
portalContext: {},
userId: 'user1',
Expand All @@ -108,20 +112,25 @@ describe('OrganizationManagementComponent', () => {
};

component.context = (() => mockGlobalContext) as any;
resourceServiceMock.readOrganizations.mockReturnValue(
of(mockOrganizations as any),
);
resourceServiceMock.list.mockReturnValue(of(mockOrganizations as any));

component.ngOnInit();

expect(resourceServiceMock.readOrganizations).toHaveBeenCalled();
expect(component.organizations()).toEqual(['org1', 'org2']);
expect(resourceServiceMock.list).toHaveBeenCalled();
expect(component.organizations()).toEqual([
{ name: 'org1', ready: true },
{ name: 'org2', ready: false },
]);
});

it('should set organization to switch', () => {
const event = { target: { value: 'testOrg' } };
component.organizations.set([{ name: 'testOrg', ready: false }]);
const event = { selectedOption: { _state: { value: 'testOrg' } } };
component.setOrganizationToSwitch(event);
expect(component.organizationToSwitch()).toBe('testOrg');
expect(component.organizationToSwitch()).toEqual({
name: 'testOrg',
ready: false,
});
});

it('should onboard new organization successfully', () => {
Expand All @@ -134,23 +143,25 @@ describe('OrganizationManagementComponent', () => {
reset: jest.fn(),
};
resourceServiceMock.create.mockReturnValue(of(mockResponse));
component.newOrganization.setValue('newOrg');
component.organizations.set(['existingOrg']);
component.newOrganizationControl.setValue('newOrg');
component.organizations.set([{ name: 'existingOrg', ready: false }]);

component.onboardOrganization();

expect(resourceServiceMock.create).toHaveBeenCalled();
expect(component.organizations()).toEqual(['newOrg', 'existingOrg']);
expect(component.organizationToSwitch()).toBe('newOrg');
expect(component.newOrganization.value).toBe('');
expect(component.organizationToSwitch()).toEqual({
name: 'newOrg',
ready: false,
});
expect(component.newOrganizationControl.value).toBe('');
expect(luigiClientMock.uxManager().showAlert).toHaveBeenCalled();
});

it('should handle organization creation error', () => {
resourceServiceMock.create.mockReturnValue(
throwError(() => new Error('Creation failed')),
);
component.newOrganization.setValue('newOrg');
component.newOrganizationControl.setValue('newOrg');

component.onboardOrganization();

Expand All @@ -177,7 +188,7 @@ describe('OrganizationManagementComponent', () => {
uiOptions: [],
};
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
component.organizationToSwitch.set('newOrg');
component.organizationToSwitch.set({ name: 'newOrg', ready: false });
Object.defineProperty(window, 'location', {
value: { protocol: 'https:', port: '8080' },
writable: true,
Expand Down Expand Up @@ -205,7 +216,10 @@ describe('OrganizationManagementComponent', () => {
uiOptions: [],
};
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
component.organizationToSwitch.set('invalid-org-name-'); // Invalid: ends with hyphen
component.organizationToSwitch.set({
name: 'invalid-org-name-',
ready: false,
}); // Invalid: ends with hyphen

await component.switchOrganization();

Expand All @@ -232,7 +246,7 @@ describe('OrganizationManagementComponent', () => {
uiOptions: [],
};
envConfigServiceMock.getEnvConfig.mockResolvedValue(mockEnvConfig);
component.organizationToSwitch.set('validorg');
component.organizationToSwitch.set({ name: 'validorg', ready: false });
Object.defineProperty(window, 'location', {
value: { protocol: 'https:', port: '' },
writable: true,
Expand Down Expand Up @@ -282,4 +296,51 @@ describe('OrganizationManagementComponent', () => {
const result = component.getValueState(formControl);
expect(result).toBe('None');
});

it('should handle error when reading organizations', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const mockError = new Error('Failed to fetch organizations');

resourceServiceMock.list.mockReturnValue(throwError(() => mockError));

component.readOrganizations();

expect(consoleSpy).toHaveBeenCalledWith(
'Error reading organizations',
mockError,
);

consoleSpy.mockRestore();
});

it('should update existing organizationToSwitch when reading organizations', () => {
const mockOrganizations = [
{
metadata: { name: 'org1' },
status: { conditions: [{ type: 'Ready', status: 'True' }] },
},
{
metadata: { name: 'org2' },
status: { conditions: [{ type: 'Ready', status: 'True' }] },
},
];

// Set an existing organization to switch
component.organizationToSwitch.set({ name: 'org2', ready: false });

resourceServiceMock.list.mockReturnValue(of(mockOrganizations as any));

component.readOrganizations();

expect(resourceServiceMock.list).toHaveBeenCalled();
expect(component.organizations()).toEqual([
{ name: 'org1', ready: true },
{ name: 'org2', ready: true },
]);
// Should find and update the existing organizationToSwitch
expect(component.organizationToSwitch()).toEqual({
name: 'org2',
ready: true,
});
});
});
Loading