Skip to content
12 changes: 12 additions & 0 deletions AMW_angular/io/src/app/resources/models/resource-dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ResourceDependency {
resourceId: number;
resourceName: string;
resourceTypeName: string;
releaseName: string;
}

export interface ResourceDependencies {
resourceName: string;
consumedRelations: ResourceDependency[];
providedRelations: ResourceDependency[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<app-loading-indicator [isLoading]="isLoading()"></app-loading-indicator>
<app-page>
<div class="page-title d-flex align-items-center gap-3">
<a
[routerLink]="['/resource/edit']"
[queryParams]="{ id: resourceId(), ctx: contextId() }"
class="btn btn-link p-0"
>
<app-icon icon="arrow-left"></app-icon>
</a>
<span class="resource-name">Dependencies for {{ dependencies()?.resourceName }}</span>
</div>

<div class="page-content">
@if (hasDependencies()) {
@if (hasConsumedRelations()) {
<div class="card">
<div class="card-header">
<h2>Resources consuming {{ dependencies()?.resourceName }}</h2>
</div>
<div class="card-body p-0">
<app-table
[entityName]="'dependencies'"
[headers]="dependenciesHeader()"
[data]="consumedRelationsTableData()"
[canNavigate]="permissions().canUseAngularEditResource"
(navigate)="navigateToResource($event.id)"
[canEdit]="permissions().canReadResources"
(edit)="openEditResourcePage($event.id)"
></app-table>
</div>
</div>
}

@if (hasProvidedRelations()) {
<div class="card">
<div class="card-header">
<h2>Resources providing {{ dependencies()?.resourceName }}</h2>
</div>
<div class="card-body p-0">
<app-table
[entityName]="'dependencies'"
[headers]="dependenciesHeader()"
[data]="providedRelationsTableData()"
[canNavigate]="permissions().canUseAngularEditResource"
(navigate)="navigateToResource($event.id)"
[canEdit]="permissions().canReadResources"
(edit)="openEditResourcePage($event.id)"
></app-table>
</div>
</div>
}
} @else {
<div class="alert alert-info">No dependencies found.</div>
}
</div>
</app-page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResourceDependenciesComponent } from './resource-dependencies.component';
import { ResourceDependenciesService } from '../services/resource-dependencies.service';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ResourceDependenciesComponent],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
ResourceDependenciesService,
{
provide: ActivatedRoute,
useValue: {
queryParamMap: of(
new Map([
['id', '1'],
['ctx', '1'],
]),
),
},
},
],
}).compileComponents();

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Component, computed, inject, Signal } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map, switchMap } from 'rxjs/operators';
import { ResourceDependenciesService } from '../services/resource-dependencies.service';
import { ResourceDependencies, ResourceDependency } from '../models/resource-dependency';
import { LoadingIndicatorComponent } from '../../shared/elements/loading-indicator.component';
import { PageComponent } from '../../layout/page/page.component';
import { IconComponent } from '../../shared/icon/icon.component';
import { TableColumnType, TableComponent } from '../../shared/table/table.component';
import { AuthService } from '../../auth/auth.service';

@Component({
selector: 'app-resource-dependencies',
standalone: true,
imports: [LoadingIndicatorComponent, PageComponent, IconComponent, TableComponent, RouterLink],
templateUrl: './resource-dependencies.component.html',
})
export class ResourceDependenciesComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
private dependenciesService = inject(ResourceDependenciesService);
private authService = inject(AuthService);

permissions = computed(() => {
if (this.authService.restrictions().length > 0) {
return {
canReadResources: this.authService.hasPermission('RESOURCE', 'READ'),
// TODO: remove after migration
canUseAngularEditResource:
this.authService.hasPermission('RESOURCE', 'READ') &&
this.authService.hasPermission('ANGULAR_EDIT_RESOURCE', 'ALL'),
};
} else {
return {
canReadResources: false,
canUseAngularEditResource: false,
};
}
});

resourceId = toSignal(this.route.queryParamMap.pipe(map((params) => Number(params.get('id')))), {
initialValue: 0,
});

contextId = toSignal(this.route.queryParamMap.pipe(map((params) => Number(params.get('ctx')))), {
initialValue: 1,
});

dependencies: Signal<ResourceDependencies | undefined> = toSignal(
this.route.queryParamMap.pipe(
map((params) => Number(params.get('id'))),
switchMap((id) => this.dependenciesService.getResourceDependencies(id)),
),
);

isLoading = computed(() => !this.dependencies());

hasConsumedRelations = computed(() => (this.dependencies()?.consumedRelations?.length ?? 0) > 0);

hasProvidedRelations = computed(() => (this.dependencies()?.providedRelations?.length ?? 0) > 0);

hasDependencies = computed(() => this.hasConsumedRelations() || this.hasProvidedRelations());

consumedRelationsTableData = computed(() =>
(this.dependencies()?.consumedRelations ?? []).map((dep) => ({ ...dep, id: dep.resourceId })),
);

providedRelationsTableData = computed(() =>
(this.dependencies()?.providedRelations ?? []).map((dep) => ({ ...dep, id: dep.resourceId })),
);

dependenciesHeader(): TableColumnType<ResourceDependency>[] {
return [
{
key: 'resourceTypeName',
columnTitle: 'Resource Type',
},
{
key: 'resourceName',
columnTitle: 'Resource Name',
},
{
key: 'releaseName',
columnTitle: 'Release',
},
];
}

navigateToResource(id: number): void {
this.router.navigate(['/resource/edit'], { queryParams: { id, ctx: 1 } });
}

openEditResourcePage(id: number) {
window.location.href = `/AMW_web/pages/editResourceView.xhtml?ctx=1&id=${id}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,34 @@
<app-page>
<div class="page-title d-flex">
<div class="resource-name">Edit {{ this.resource()?.name }}</div>
@if (showAnalyze()) {
<div ngbDropdown>
<app-button [additionalClasses]="'dropdown-toggle'" [variant]="'secondary'" ngbDropdownToggle>
Analyze
</app-button>
<ul ngbDropdownMenu>
@if (testGenerationAvailable()) {
<li ngbDropdownItem>
<a
class="dropdown-item"
[routerLink]="['/resource/test-generation']"
[queryParams]="testGenerationQueryParams()"
>
Test Generation
</a>
</li>
}
</ul>
</div>
}

<div ngbDropdown>
<app-button [additionalClasses]="'dropdown-toggle'" [variant]="'secondary'" ngbDropdownToggle>
Analyze
</app-button>
<ul ngbDropdownMenu>
@if (testGenerationAvailable()) {
<li ngbDropdownItem>
<a
class="dropdown-item"
[routerLink]="['/resource/test-generation']"
[queryParams]="testGenerationQueryParams()"
>
Test Generation
</a>
</li>
}
<li ngbDropdownItem>
<a
class="dropdown-item"
[routerLink]="['/resource/dependencies']"
[queryParams]="{ id: id(), ctx: contextId() }"
>
Show Dependencies
</a>
</li>
</ul>
</div>
@if (showMore()) {
<div ngbDropdown>
<app-button [additionalClasses]="'dropdown-toggle'" [variant]="'secondary'" ngbDropdownToggle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class ResourceEditComponent {
id: this.id(),
ctx: this.contextId(),
}));

protected readonly showAnalyze = computed<boolean>(
() => this.testGenerationAvailable() && this.permissions().canTestGenerate,
);
Expand Down
2 changes: 2 additions & 0 deletions AMW_angular/io/src/app/resources/resources.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { ResourceEditComponent } from './resource-edit/resource-edit.component';
import { ResourcesPageComponent } from './resources-page.component';
import { ResourceTypeEditComponent } from './resource-type-edit/resource-type-edit.component';
import { TestGenerationComponent } from './test-generation/test-generation.component';
import { ResourceDependenciesComponent } from './resource-dependencies/resource-dependencies.component';

export const resourcesRoute = [
{ path: 'resources', component: ResourcesPageComponent, title: 'Resources - Liima' },
{ path: 'resource/edit', component: ResourceEditComponent, title: 'Edit Resource - Liima' },
{ path: 'resourceType/edit', component: ResourceTypeEditComponent, title: 'Edit Resource Type - Liima' },
{ path: 'resource/test-generation', component: TestGenerationComponent, title: 'Test Generation - Liima' },
{ path: 'resource/dependencies', component: ResourceDependenciesComponent, title: 'Resource Dependencies - Liima' },
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ResourceDependencies } from '../models/resource-dependency';
import { BaseService } from '../../base/base.service';

@Injectable({
providedIn: 'root',
})
export class ResourceDependenciesService extends BaseService {
private http = inject(HttpClient);
private path = `${this.getBaseUrl()}/resources`;

getResourceDependencies(resourceId: number): Observable<ResourceDependencies> {
return this.http.get<ResourceDependencies>(`${this.path}/${resourceId}/resource-dependencies`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
public interface GetResourceUseCase {

ResourceEntity getResourceById(ResourceIdCommand command) throws ResourceNotFoundException;

ResourceEntity getWithGroupAndRelatedResources(Integer resourceId) throws ResourceNotFoundException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import ch.puzzle.itc.mobiliar.business.resourcerelation.entity.ConsumedResourceRelationEntity;
import ch.puzzle.itc.mobiliar.business.utils.ValidationHelper;
import ch.puzzle.itc.mobiliar.common.exception.NotFoundException;
import ch.puzzle.itc.mobiliar.common.exception.ResourceNotFoundException;
import ch.puzzle.itc.mobiliar.common.exception.ValidationException;
import ch.puzzle.itc.mobiliar.common.util.ConfigKey;
import ch.puzzle.itc.mobiliar.common.util.ConfigurationService;
Expand Down Expand Up @@ -246,5 +247,4 @@ public ResourceEntity getResourceById(Integer resourceId) {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.GetResourceUseCase;
import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceIdCommand;
import ch.puzzle.itc.mobiliar.business.resourcegroup.boundary.ResourceLocator;
import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity;
import ch.puzzle.itc.mobiliar.common.exception.ResourceNotFoundException;

Expand All @@ -15,6 +16,8 @@ public class ResourceService implements GetResourceUseCase {
@Inject
private ResourceRepository resourceRepository;

@Inject
private ResourceLocator resourceLocator;

@Override
public ResourceEntity getResourceById(ResourceIdCommand command) throws ResourceNotFoundException {
Expand All @@ -24,4 +27,13 @@ public ResourceEntity getResourceById(ResourceIdCommand command) throws Resource
throw new ResourceNotFoundException("Resource with id " + command.getResourceId() + "not found!");
}
}

@Override
public ResourceEntity getWithGroupAndRelatedResources(Integer resourceId) throws ResourceNotFoundException {
ResourceEntity resource = resourceLocator.getResourceWithGroupAndRelatedResources(resourceId);
if (resource == null) {
throw new ResourceNotFoundException("Resource with id " + resourceId + " not found");
}
return resource;
}
}
Loading
Loading