Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
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