Skip to content

Commit ed2a14c

Browse files
Add kcpPath to Luigi context configuration and processing (#98)
* Add `entityName` and `accountPath` to Luigi context configuration and processing * Add `entityName` and `accountPath` to Luigi context configuration and processing * Add kcpPath to node context * Add kcpPath to node context * Add kcpPath to node context * fix tests * fix tests * Adjust kcp path calculation * fix tests * Add kcpPath to Luigi context configuration and processing * Adjust kcp path calculation * fix tests --------- Co-authored-by: Grzegorz Krajniak <[email protected]>
1 parent 1ec61fe commit ed2a14c

13 files changed

+78
-169
lines changed

projects/lib/portal-options/models/luigi-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface PortalNodeContext extends NodeContext {
1717
translationTable?: any;
1818
namespaceId?: string;
1919
entity?: Resource;
20+
entityName?: string;
2021
entityId?: string;
2122
entityContext?: PortalEntityContext;
2223
}

projects/lib/portal-options/models/luigi-node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import { PortalNodeContext } from './luigi-context';
22
import { LuigiNode } from '@openmfp/portal-ui-lib';
33

44
export interface PortalLuigiNode extends LuigiNode {
5-
context?: PortalNodeContext;
5+
context: PortalNodeContext;
66
parent?: PortalLuigiNode;
77
}

projects/lib/portal-options/services/crd-gateway-kcp-patch-resolver.service.spec.ts

Lines changed: 15 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,26 @@ describe('CrdGatewayKcpPatchResolver', () => {
6161
).toHaveBeenCalledWith(`${kcpRootOrgsPath}:org1:acc1:acc2:acc3`);
6262
});
6363

64-
it('should use kcpPath from node context if provided', async () => {
64+
it('should set kcpPath for node context if not provided', async () => {
65+
const node: PortalLuigiNode = {
66+
context: {},
67+
parent: undefined,
68+
} as any;
69+
70+
await resolver.resolveCrdGatewayKcpPath(node);
71+
72+
expect(node.context.kcpPath).toEqual('root:orgs:org1');
73+
});
74+
75+
it('should not set kcpPath for node context if present', async () => {
6576
const node: PortalLuigiNode = {
6677
context: { kcpPath: 'customPath' },
6778
parent: undefined,
6879
} as any;
6980

7081
await resolver.resolveCrdGatewayKcpPath(node);
7182

72-
expect(
73-
gatewayServiceMock.updateCrdGatewayUrlWithEntityPath,
74-
).toHaveBeenCalledWith('customPath');
83+
expect(node.context.kcpPath).toEqual('customPath');
7584
});
7685

7786
it('should handle node without entity metadata', async () => {
@@ -84,43 +93,7 @@ describe('CrdGatewayKcpPatchResolver', () => {
8493
).toHaveBeenCalledWith(`${kcpRootOrgsPath}:org1`);
8594
});
8695

87-
describe('resolveCrdGatewayKcpPathForNextAccountEntity', () => {
88-
it('should return early if kind is not Account', async () => {
89-
const nextNode: PortalLuigiNode = {
90-
context: {},
91-
parent: undefined,
92-
} as any;
93-
94-
await resolver.resolveCrdGatewayKcpPathForNextAccountEntity(
95-
'leafAcc',
96-
'Project',
97-
nextNode,
98-
);
99-
100-
expect(
101-
gatewayServiceMock.updateCrdGatewayUrlWithEntityPath,
102-
).not.toHaveBeenCalled();
103-
expect(envConfigServiceMock.getEnvConfig).not.toHaveBeenCalled();
104-
});
105-
106-
it('should return early if entityId is empty', async () => {
107-
const nextNode: PortalLuigiNode = {
108-
context: {},
109-
parent: undefined,
110-
} as any;
111-
112-
await resolver.resolveCrdGatewayKcpPathForNextAccountEntity(
113-
'',
114-
'Account',
115-
nextNode,
116-
);
117-
118-
expect(
119-
gatewayServiceMock.updateCrdGatewayUrlWithEntityPath,
120-
).not.toHaveBeenCalled();
121-
expect(envConfigServiceMock.getEnvConfig).not.toHaveBeenCalled();
122-
});
123-
96+
describe('resolveCrdGatewayKcpPath', () => {
12497
it('should aggregate parent Account entities and append entityId', async () => {
12598
const nextNode: PortalLuigiNode = {
12699
context: {},
@@ -142,38 +115,12 @@ describe('CrdGatewayKcpPatchResolver', () => {
142115
},
143116
} as any;
144117

145-
await resolver.resolveCrdGatewayKcpPathForNextAccountEntity(
146-
'leafAcc',
147-
'Account',
148-
nextNode,
149-
);
118+
await resolver.resolveCrdGatewayKcpPath(nextNode, 'leafAcc', 'Account');
150119

151120
expect(envConfigServiceMock.getEnvConfig).toHaveBeenCalled();
152121
expect(
153122
gatewayServiceMock.updateCrdGatewayUrlWithEntityPath,
154123
).toHaveBeenCalledWith(`${kcpRootOrgsPath}:org1:acc1:acc2:leafAcc`);
155124
});
156-
157-
it('should use kcpPath from node context if provided (override)', async () => {
158-
const nextNode: PortalLuigiNode = {
159-
context: { kcpPath: 'overridePath' },
160-
parent: {
161-
context: {
162-
entity: { metadata: { name: 'accParent' }, __typename: 'Account' },
163-
},
164-
parent: undefined,
165-
},
166-
} as any;
167-
168-
await resolver.resolveCrdGatewayKcpPathForNextAccountEntity(
169-
'leafAcc',
170-
'Account',
171-
nextNode,
172-
);
173-
174-
expect(
175-
gatewayServiceMock.updateCrdGatewayUrlWithEntityPath,
176-
).toHaveBeenCalledWith('overridePath');
177-
});
178125
});
179126
});

projects/lib/portal-options/services/crd-gateway-kcp-patch-resolver.service.ts

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ export class CrdGatewayKcpPatchResolver {
99
private gatewayService = inject(GatewayService);
1010
private envConfigService = inject(EnvConfigService);
1111

12-
public async resolveCrdGatewayKcpPathForNextAccountEntity(
13-
entityId: string,
14-
kind: string,
12+
public async resolveCrdGatewayKcpPath(
1513
nextNode: PortalLuigiNode,
14+
entityId?: string,
15+
kind?: string,
1616
) {
17-
if (kind !== 'Account' || !entityId) {
18-
return;
17+
if (nextNode.context.kcpPath) {
18+
this.gatewayService.updateCrdGatewayUrlWithEntityPath(
19+
nextNode.context.kcpPath,
20+
);
21+
return nextNode.context.kcpPath;
1922
}
2023

21-
let entityKcpPath = `:${entityId}`;
22-
let node: PortalLuigiNode | undefined = nextNode.parent;
23-
24+
let entityKcpPath = kind !== 'Account' || !entityId ? '' : `:${entityId}`;
25+
let node: PortalLuigiNode | undefined = nextNode;
2426
do {
2527
const entity = node?.context?.entity;
2628
if (entity?.metadata?.name && entity['__typename'] === 'Account') {
@@ -30,25 +32,12 @@ export class CrdGatewayKcpPatchResolver {
3032
} while (node);
3133

3234
const org = (await this.envConfigService.getEnvConfig()).idpName;
33-
const kcpPath =
34-
nextNode.context?.kcpPath || `${kcpRootOrgsPath}:${org}${entityKcpPath}`;
35+
const kcpPath = `${kcpRootOrgsPath}:${org}${entityKcpPath}`;
3536
this.gatewayService.updateCrdGatewayUrlWithEntityPath(kcpPath);
36-
}
3737

38-
public async resolveCrdGatewayKcpPath(nextNode: PortalLuigiNode) {
39-
let entityKcpPath = '';
40-
let node: PortalLuigiNode | undefined = nextNode;
41-
do {
42-
const entity = node.context?.entity;
43-
if (entity?.metadata?.name && entity['__typename'] === 'Account') {
44-
entityKcpPath = `:${entity.metadata.name}${entityKcpPath}`;
45-
}
46-
node = node.parent;
47-
} while (node);
48-
49-
const org = (await this.envConfigService.getEnvConfig()).idpName;
50-
const kcpPath =
51-
nextNode.context?.kcpPath || `${kcpRootOrgsPath}:${org}${entityKcpPath}`;
52-
this.gatewayService.updateCrdGatewayUrlWithEntityPath(kcpPath);
38+
if (!nextNode.context.kcpPath) {
39+
nextNode.context.kcpPath = kcpPath;
40+
}
41+
return kcpPath;
5342
}
5443
}

projects/lib/portal-options/services/custom-global-nodes.service.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,17 @@ export class CustomGlobalNodesServiceImpl implements CustomGlobalNodesService {
3030
showBreadcrumbs: false,
3131
hideSideNav: true,
3232
hideFromNav: true,
33+
context: {} as PortalNodeContext,
3334
entityType: 'global',
3435
children: [
3536
{
36-
context: {
37-
profileUserId: ':profileUserId',
38-
} as unknown as PortalNodeContext,
37+
pathSegment: ':userId',
38+
hideSideNav: true,
39+
hideFromNav: true,
3940
defineEntity: {
4041
id: 'user',
41-
contextKey: 'profileUserId',
4242
},
43-
pathSegment: ':profileUserId',
44-
hideSideNav: true,
45-
hideFromNav: true,
43+
context: {} as PortalNodeContext,
4644
children: [
4745
{
4846
pathSegment: 'overview',

projects/lib/portal-options/services/luigi-extended-global-context-config.service.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ describe('LuigiExtendedGlobalContextConfigServiceImpl', () => {
8888
expect(result).toEqual({
8989
organizationId: 'originClusterId/test-org',
9090
kcpCA: 'dW5kZWZpbmVk',
91+
kcpPath: 'root:orgs:test-org',
9192
organization: 'test-org',
9293
entityId: 'originClusterId/test-org',
94+
entityName: 'test-org',
9395
});
9496

9597
expect(mockResourceService.readAccountInfo).toHaveBeenCalledWith({

projects/lib/portal-options/services/luigi-extended-global-context-config.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { kcpRootOrgsPath } from '../models/constants';
12
import { Injectable, inject } from '@angular/core';
23
import {
34
AuthService,
@@ -48,7 +49,9 @@ export class LuigiExtendedGlobalContextConfigServiceImpl
4849
organization: entityId,
4950
organizationId: `${organizationOriginClusterId}/${entityId}`,
5051
kcpCA: btoa(accountInfo?.spec?.clusterInfo?.ca),
52+
kcpPath: `${kcpRootOrgsPath}:${entityId}`,
5153
entityId: `${organizationOriginClusterId}/${entityId}`, // if no entity selected the entityId is the same as the organizationId
54+
entityName: entityId,
5255
};
5356
} catch (e) {
5457
console.error(`Failed to read entity ${entityId} from ${operation}`, e);

projects/lib/portal-options/services/node-context-processing.service.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('NodeContextProcessingServiceImpl', () => {
1818
} as unknown as jest.Mocked<ResourceService>;
1919

2020
mockCrdGatewayKcpPatchResolver = {
21-
resolveCrdGatewayKcpPathForNextAccountEntity: jest.fn(),
21+
resolveCrdGatewayKcpPath: jest.fn(),
2222
} as unknown as jest.Mocked<CrdGatewayKcpPatchResolver>;
2323

2424
TestBed.configureTestingModule({
@@ -65,8 +65,8 @@ describe('NodeContextProcessingServiceImpl', () => {
6565
await service.processNodeContext(entityId, entityNode, ctx);
6666

6767
expect(
68-
mockCrdGatewayKcpPatchResolver.resolveCrdGatewayKcpPathForNextAccountEntity,
69-
).toHaveBeenCalledWith(entityId, 'TestKind', entityNode);
68+
mockCrdGatewayKcpPatchResolver.resolveCrdGatewayKcpPath,
69+
).toHaveBeenCalledWith(entityNode, entityId, 'TestKind');
7070
expect(mockResourceService.read).toHaveBeenCalled();
7171
});
7272
});
@@ -431,4 +431,4 @@ describe('NodeContextProcessingServiceImpl', () => {
431431
);
432432
});
433433
});
434-
});
434+
});

projects/lib/portal-options/services/node-context-processing.service.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { ResourceService } from '@platform-mesh/portal-ui-lib/services';
77
import { replaceDotsAndHyphensWithUnderscores } from '@platform-mesh/portal-ui-lib/utils';
88
import { firstValueFrom } from 'rxjs';
99

10-
1110
@Injectable({
1211
providedIn: 'root',
1312
})
@@ -30,11 +29,12 @@ export class NodeContextProcessingServiceImpl
3029
return;
3130
}
3231

33-
await this.crdGatewayKcpPatchResolver.resolveCrdGatewayKcpPathForNextAccountEntity(
34-
entityId,
35-
kind,
36-
entityNode,
37-
);
32+
const kcpPath =
33+
await this.crdGatewayKcpPatchResolver.resolveCrdGatewayKcpPath(
34+
entityNode,
35+
entityId,
36+
kind,
37+
);
3838

3939
const operation = replaceDotsAndHyphensWithUnderscores(group);
4040
const namespaceId =
@@ -66,13 +66,15 @@ export class NodeContextProcessingServiceImpl
6666
);
6767

6868
// update the current already calculated by Luigi context for a node
69+
ctx.kcpPath = kcpPath;
6970
ctx.entity = entity;
71+
ctx.entityName = entityId;
7072
ctx.entityId = `${entity.metadata?.annotations?.['kcp.io/cluster']}/${entityId}`;
7173
// update the node context of sa node to contain the entity for future context calculations
72-
if (entityNode.context) {
73-
entityNode.context.entity = entity;
74-
entityNode.context.entityId = ctx.entityId;
75-
}
74+
entityNode.context.kcpPath = kcpPath;
75+
entityNode.context.entity = entity;
76+
entityNode.context.entityName = ctx.entityName;
77+
entityNode.context.entityId = ctx.entityId;
7678
} catch (e) {
7779
console.error(`Not able to read entity ${entityId} from ${operation}`);
7880
}

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

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { GatewayService } from './gateway.service';
22
import { TestBed } from '@angular/core/testing';
33
import { LuigiCoreService } from '@openmfp/portal-ui-lib';
44

5-
65
describe('GatewayService', () => {
76
let service: GatewayService;
87
let mockLuigiCoreService: any;
@@ -85,7 +84,6 @@ describe('GatewayService', () => {
8584
},
8685
token: 'token',
8786
accountId: 'entityId',
88-
kcpPath: ':org1',
8987
};
9088
const result = service.resolveKcpPath(nodeContext, true);
9189
expect(result).toBe(':org1');
@@ -130,8 +128,6 @@ describe('GatewayService', () => {
130128
});
131129

132130
it('should show error alert and return empty string for invalid URL', () => {
133-
const showAlertSpy = jest.spyOn(mockLuigiCoreService, 'showAlert');
134-
135131
const nodeContext = {
136132
portalContext: {
137133
crdGatewayApiUrl: 'https://example.com/invalid-url',
@@ -143,15 +139,9 @@ describe('GatewayService', () => {
143139
const result = service.resolveKcpPath(nodeContext);
144140

145141
expect(result).toBe('');
146-
expect(showAlertSpy).toHaveBeenCalledWith({
147-
text: 'Could not get current KCP path from gateway URL',
148-
type: 'error',
149-
});
150142
});
151143

152144
it('should show error alert and return empty string for URL without /graphql suffix', () => {
153-
const showAlertSpy = jest.spyOn(mockLuigiCoreService, 'showAlert');
154-
155145
const nodeContext = {
156146
portalContext: {
157147
crdGatewayApiUrl: 'https://example.com/:org1:acc1/api',
@@ -163,15 +153,9 @@ describe('GatewayService', () => {
163153
const result = service.resolveKcpPath(nodeContext);
164154

165155
expect(result).toBe('');
166-
expect(showAlertSpy).toHaveBeenCalledWith({
167-
text: 'Could not get current KCP path from gateway URL',
168-
type: 'error',
169-
});
170156
});
171157

172158
it('should show error alert and return empty string for empty URL', () => {
173-
const showAlertSpy = jest.spyOn(mockLuigiCoreService, 'showAlert');
174-
175159
const nodeContext = {
176160
portalContext: {
177161
crdGatewayApiUrl: '',
@@ -183,10 +167,6 @@ describe('GatewayService', () => {
183167
const result = service.resolveKcpPath(nodeContext);
184168

185169
expect(result).toBe('');
186-
expect(showAlertSpy).toHaveBeenCalledWith({
187-
text: 'Could not get current KCP path from gateway URL',
188-
type: 'error',
189-
});
190170
});
191171
});
192172
});

0 commit comments

Comments
 (0)