Skip to content

Commit

Permalink
Resources page with routing and list of resource types (#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
llorentelemmc authored Nov 28, 2024
1 parent 38f8694 commit 43b4b4f
Show file tree
Hide file tree
Showing 18 changed files with 354 additions and 56 deletions.
2 changes: 2 additions & 0 deletions AMW_angular/io/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { deploymentRoutes } from './deployment/deployment-routes';
import { settingsRoutes } from './settings/settings.routes';
import { deploymentsRoutes } from './deployments/deployments.routes';
import { serversRoute } from './servers/servers.route';
import { resourcesRoute } from './resources/resources.route';

export const routes: Routes = [
// default route only, the rest is done in module routing
{ path: '', component: DeploymentsComponent },

...appsRoutes,
...serversRoute,
...resourcesRoute,
...settingsRoutes,
...auditviewRoutes,
...deploymentRoutes,
Expand Down
2 changes: 1 addition & 1 deletion AMW_angular/io/src/app/navigation/navigation.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
</li>
<li class="nav-item">
<a class="nav-link" href="/AMW_web/pages/resourceList.xhtml">Resources</a>
<a class="nav-link" href="#/resources">Resources</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#/deployments/">Deploy</a>
Expand Down
2 changes: 2 additions & 0 deletions AMW_angular/io/src/app/resource/resource-type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface ResourceType {
id: number;
name: string;
hasChildren: boolean;
children: ResourceType[];
}
12 changes: 0 additions & 12 deletions AMW_angular/io/src/app/resource/resource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Resource } from './resource';
import { ResourceType } from './resource-type';
import { Release } from './release';
import { Relation } from './relation';
import { Property } from './property';
Expand Down Expand Up @@ -66,17 +65,6 @@ export class ResourceService extends BaseService {
);
}

getAllResourceTypes(): Observable<ResourceType[]> {
return this.http
.get<ResourceType[]>(`${this.getBaseUrl()}/resources/resourceTypes`, {
headers: this.getHeaders(),
})
.pipe(
map((resources) => resources.map(toResource)),
catchError(this.handleError),
);
}

getByType(type: string): Observable<Resource[]> {
return this.http
.get<Resource[]>(`${this.getBaseUrl()}/resources?type=${type}`, {
Expand Down
43 changes: 43 additions & 0 deletions AMW_angular/io/src/app/resources/resource-types.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, startWith, Subject } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
import { shareReplay, switchMap } from 'rxjs/operators';
import { BaseService } from '../base/base.service';
import { ResourceType } from '../resource/resource-type';

@Injectable({ providedIn: 'root' })
export class ResourceTypesService extends BaseService {
private reload$ = new Subject<ResourceType[]>();

private predefinedResourceTypes$ = this.getPredefinedResourceTypes();

private rootResourceTypes$ = this.reload$.pipe(
startWith(null),
switchMap(() => this.getRootResourceTypes()),
shareReplay(1),
);

predefinedResourceTypes = toSignal(this.predefinedResourceTypes$, { initialValue: [] as ResourceType[] });
rootResourceTypes = toSignal(this.rootResourceTypes$, { initialValue: [] as ResourceType[] });

constructor(private http: HttpClient) {
super();
}

getAllResourceTypes(): Observable<ResourceType[]> {
return this.http.get<ResourceType[]>(`${this.getBaseUrl()}/resources/resourceTypes`);
}

getPredefinedResourceTypes(): Observable<ResourceType[]> {
return this.http.get<ResourceType[]>(`${this.getBaseUrl()}/resources/predefinedResourceTypes`);
}

getRootResourceTypes(): Observable<ResourceType[]> {
return this.http.get<ResourceType[]>(`${this.getBaseUrl()}/resources/rootResourceTypes`);
}

refreshData() {
this.reload$.next([]);
}
}
31 changes: 31 additions & 0 deletions AMW_angular/io/src/app/resources/resources-page.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<app-loading-indicator [isLoading]="isLoading()"></app-loading-indicator>
<app-page>
<div class="page-title">Resources</div>
<div class="page-content">
@if (permissions().canViewResourceTypes) {
<nav class="nav flex-column">
@for (resourceType of predefinedResourceTypes(); track resourceType.name) {
<a>{{ resourceType.name }}</a>
}
</nav>
<nav class="nav flex-column">
@for (resourceType of rootResourceTypes(); track resourceType.name) {
<a (click)="toggleChildren(resourceType)">
{{ resourceType.name }}
@if (resourceType.hasChildren) {
<span>{{ expandedResourceTypeId === resourceType.id ? '-' : '+' }}</span>
}
</a>
@if (resourceType.hasChildren && expandedResourceTypeId === resourceType.id) {
<ul class="subnav">
@for (child of resourceType.children; track child.name) {
<div class="grid-item">
<a>{{ child.name }}</a>
</div>
}
</ul>
} }
</nav>
}
</div>
</app-page>
24 changes: 24 additions & 0 deletions AMW_angular/io/src/app/resources/resources-page.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResourcesPageComponent } from './resources-page.component';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';

describe('ResourcesPageComponent', () => {
let component: ResourcesPageComponent;
let fixture: ComponentFixture<ResourcesPageComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ResourcesPageComponent],
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()],
}).compileComponents();

fixture = TestBed.createComponent(ResourcesPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
37 changes: 37 additions & 0 deletions AMW_angular/io/src/app/resources/resources-page.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ChangeDetectionStrategy, Component, computed, inject, Signal, signal } from '@angular/core';
import { AuthService } from '../auth/auth.service';
import { PageComponent } from '../layout/page/page.component';
import { LoadingIndicatorComponent } from '../shared/elements/loading-indicator.component';
import { ResourceTypesService } from './resource-types.service';
import { ResourceType } from '../resource/resource-type';

@Component({
selector: 'app-resources-page',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [PageComponent, LoadingIndicatorComponent],
templateUrl: './resources-page.component.html',
})
export class ResourcesPageComponent {
private authService = inject(AuthService);
private resourceTypesService = inject(ResourceTypesService);

predefinedResourceTypes: Signal<ResourceType[]> = this.resourceTypesService.predefinedResourceTypes;
rootResourceTypes: Signal<ResourceType[]> = this.resourceTypesService.rootResourceTypes;
isLoading = signal(false);
expandedResourceTypeId: number | null = null;

permissions = computed(() => {
if (this.authService.restrictions().length > 0) {
return {
canViewResourceTypes: this.authService.hasPermission('RES_TYPE_LIST_TAB', 'ALL'),
};
} else {
return { canViewResourceTypes: false };
}
});

toggleChildren(resourceType: ResourceType): void {
this.expandedResourceTypeId = this.expandedResourceTypeId === resourceType.id ? null : resourceType.id;
}
}
3 changes: 3 additions & 0 deletions AMW_angular/io/src/app/resources/resources.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ResourcesPageComponent } from './resources-page.component';

export const resourcesRoute = [{ path: 'resources', component: ResourcesPageComponent }];
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Restriction } from './restriction';
import { Tag } from './tag';
import { EnvironmentService } from '../../deployment/environment.service';
import { ResourceService } from '../../resource/resource.service';
import { ResourceTypesService } from '../../resources/resource-types.service';
import { Environment } from 'src/app/deployment/environment';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
Expand All @@ -27,6 +28,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
let permissionService: PermissionService;
let environmentService: EnvironmentService;
let resourceService: ResourceService;
let resourceTypesService: ResourceTypesService;

const mockRoute: any = { snapshot: {} };
mockRoute.params = new Subject<any>();
Expand All @@ -50,6 +52,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
EnvironmentService,
PermissionService,
ResourceService,
ResourceTypesService,
{ provide: ActivatedRoute, useValue: mockRoute },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
Expand All @@ -63,6 +66,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
permissionService = TestBed.inject(PermissionService);
environmentService = TestBed.inject(EnvironmentService);
resourceService = TestBed.inject(ResourceService);
resourceTypesService = TestBed.inject(ResourceTypesService);
});

it('should have default data', () => {
Expand All @@ -80,7 +84,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
} as Environment,
]);
expect(component.resourceGroups).toEqual([]);
expect(component.resourceTypes).toEqual([{ id: null, name: null }]);
expect(component.resourceTypes).toEqual([{ id: null, name: null, hasChildren: false, children: [] }]);
expect(component.restrictionType).toEqual('role');
});

Expand All @@ -98,7 +102,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
spyOn(permissionService, 'getAllPermissionEnumValues').and.returnValue(of(permissions));
spyOn(environmentService, 'getAllIncludingGroups').and.returnValue(of(environments));
spyOn(resourceService, 'getAllResourceGroups').and.callThrough();
spyOn(resourceService, 'getAllResourceTypes').and.callThrough();
spyOn(resourceTypesService, 'getAllResourceTypes').and.callThrough();
// when
component.ngOnInit();
mockRoute.params.next({ restrictionType: 'role' });
Expand All @@ -109,7 +113,7 @@ describe('PermissionComponent without any params (default: type Role)', () => {
expect(permissionService.getAllPermissionEnumValues).toHaveBeenCalled();
expect(environmentService.getAllIncludingGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceTypes).toHaveBeenCalled();
expect(resourceTypesService.getAllResourceTypes).toHaveBeenCalled();
expect(component.permissions).toEqual(permissions);
expect(component.restriction).toBeNull();
expect(component.groupedEnvironments['All']).toContain({
Expand Down Expand Up @@ -402,6 +406,7 @@ describe('PermissionComponent with param restrictionType (type User)', () => {
let permissionService: PermissionService;
let environmentService: EnvironmentService;
let resourceService: ResourceService;
let resourceTypesService: ResourceTypesService;

const mockRoute: any = { snapshot: {} };
mockRoute.params = new Subject<any>();
Expand All @@ -424,6 +429,7 @@ describe('PermissionComponent with param restrictionType (type User)', () => {
EnvironmentService,
PermissionService,
ResourceService,
ResourceTypesService,
{ provide: ActivatedRoute, useValue: mockRoute },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
Expand All @@ -436,6 +442,7 @@ describe('PermissionComponent with param restrictionType (type User)', () => {
permissionService = TestBed.inject(PermissionService);
environmentService = TestBed.inject(EnvironmentService);
resourceService = TestBed.inject(ResourceService);
resourceTypesService = TestBed.inject(ResourceTypesService);
});

it('should invoke some services on ngOnInt', () => {
Expand All @@ -452,7 +459,7 @@ describe('PermissionComponent with param restrictionType (type User)', () => {
spyOn(permissionService, 'getAllPermissionEnumValues').and.returnValue(of(permissions));
spyOn(environmentService, 'getAllIncludingGroups').and.returnValue(of(environments));
spyOn(resourceService, 'getAllResourceGroups').and.callThrough();
spyOn(resourceService, 'getAllResourceTypes').and.callThrough();
spyOn(resourceTypesService, 'getAllResourceTypes').and.callThrough();
spyOn(permissionService, 'getAllUserRestrictionNames').and.callThrough();
// when
component.ngOnInit();
Expand All @@ -463,7 +470,7 @@ describe('PermissionComponent with param restrictionType (type User)', () => {
expect(permissionService.getAllPermissionEnumValues).toHaveBeenCalled();
expect(environmentService.getAllIncludingGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceTypes).toHaveBeenCalled();
expect(resourceTypesService.getAllResourceTypes).toHaveBeenCalled();
expect(permissionService.getAllUserRestrictionNames).toHaveBeenCalled();
expect(component.permissions).toEqual(permissions);
expect(component.restriction).toBeNull();
Expand Down Expand Up @@ -502,6 +509,7 @@ describe('PermissionComponent with param actingUser (delegation mode)', () => {
let permissionService: PermissionService;
let environmentService: EnvironmentService;
let resourceService: ResourceService;
let resourceTypesService: ResourceTypesService;

const mockRoute: any = { snapshot: {} };
mockRoute.params = new Subject<any>();
Expand All @@ -524,6 +532,7 @@ describe('PermissionComponent with param actingUser (delegation mode)', () => {
EnvironmentService,
PermissionService,
ResourceService,
ResourceTypesService,
{ provide: ActivatedRoute, useValue: mockRoute },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
Expand All @@ -536,6 +545,7 @@ describe('PermissionComponent with param actingUser (delegation mode)', () => {
permissionService = TestBed.inject(PermissionService);
environmentService = TestBed.inject(EnvironmentService);
resourceService = TestBed.inject(ResourceService);
resourceTypesService = TestBed.inject(ResourceTypesService);
});
it('should invoke some services on ngOnInt', () => {
// given
Expand Down Expand Up @@ -565,7 +575,7 @@ describe('PermissionComponent with param actingUser (delegation mode)', () => {
spyOn(permissionService, 'getOwnUserAndRoleRestrictions').and.returnValue(of(restrictions));
spyOn(environmentService, 'getAllIncludingGroups').and.returnValue(of(environments));
spyOn(resourceService, 'getAllResourceGroups').and.callThrough();
spyOn(resourceService, 'getAllResourceTypes').and.callThrough();
spyOn(resourceTypesService, 'getAllResourceTypes').and.callThrough();
// when
component.ngOnInit();
mockRoute.params.next({ actingUser: 'testUser' });
Expand All @@ -578,7 +588,7 @@ describe('PermissionComponent with param actingUser (delegation mode)', () => {
expect(component.userNames).not.toContain('testUser');
expect(environmentService.getAllIncludingGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceGroups).toHaveBeenCalled();
expect(resourceService.getAllResourceTypes).toHaveBeenCalled();
expect(resourceTypesService.getAllResourceTypes).toHaveBeenCalled();
expect(permissionService.getOwnUserAndRoleRestrictions).toHaveBeenCalled();
expect(component.assignableRestrictions).toEqual(restrictions);
expect(component.assignablePermissions).toEqual(permissions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '@ng-bootstrap/ng-bootstrap';
import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component';
import { ButtonComponent } from '../../shared/button/button.component';
import { ResourceTypesService } from '../../resources/resource-types.service';

@Component({
selector: 'app-permission',
Expand Down Expand Up @@ -62,7 +63,7 @@ export class PermissionComponent implements OnInit {
Global: [],
};
resourceGroups: Resource[] = [];
resourceTypes: ResourceType[] = [{ id: null, name: null }];
resourceTypes: ResourceType[] = [{ id: null, name: null, hasChildren: false, children: [] }];

defaultNavItem: string = 'Roles';
// role | user
Expand Down Expand Up @@ -90,6 +91,7 @@ export class PermissionComponent implements OnInit {
private permissionService: PermissionService,
private environmentService: EnvironmentService,
private resourceService: ResourceService,
private resourceTypesService: ResourceTypesService,
private activatedRoute: ActivatedRoute,
private location: Location,
) {
Expand Down Expand Up @@ -392,7 +394,7 @@ export class PermissionComponent implements OnInit {

private getAllResourceTypes() {
this.isLoading = true;
this.resourceService.getAllResourceTypes().subscribe({
this.resourceTypesService.getAllResourceTypes().subscribe({
next: (r) => (this.resourceTypes = this.resourceTypes.concat(r)),
error: (e) => (this.errorMessage = e),
complete: () => (this.isLoading = false),
Expand Down
Loading

0 comments on commit 43b4b4f

Please sign in to comment.