diff --git a/AMW_angular/io/src/app/apps/apps.component.ts b/AMW_angular/io/src/app/apps/apps.component.ts index 88dc877a1..a543c4717 100644 --- a/AMW_angular/io/src/app/apps/apps.component.ts +++ b/AMW_angular/io/src/app/apps/apps.component.ts @@ -70,8 +70,8 @@ export class AppsComponent implements OnInit, OnDestroy { permissions = computed(() => { if (this.authService.restrictions().length > 0) { return { - canCreateApp: this.authService.hasResourcePermission('RESOURCE', 'CREATE', 'APPLICATION'), - canCreateAppServer: this.authService.hasResourcePermission('RESOURCE', 'CREATE', 'APPLICATIONSERVER'), + canCreateApp: this.authService.hasResourceTypePermission('RESOURCE', 'CREATE', 'APPLICATION'), + canCreateAppServer: this.authService.hasResourceTypePermission('RESOURCE', 'CREATE', 'APPLICATIONSERVER'), canViewAppList: this.authService.hasPermission('APP_AND_APPSERVER_LIST', 'READ'), }; } else { diff --git a/AMW_angular/io/src/app/auth/auth.service.ts b/AMW_angular/io/src/app/auth/auth.service.ts index ef5593823..8683fec38 100644 --- a/AMW_angular/io/src/app/auth/auth.service.ts +++ b/AMW_angular/io/src/app/auth/auth.service.ts @@ -7,6 +7,14 @@ import { Restriction } from '../settings/permission/restriction'; import { toSignal } from '@angular/core/rxjs-interop'; import { DefaultResourceType } from './defaultResourceType'; +export enum Action { + READ = 'READ', + CREATE = 'CREATE', + UPDATE = 'UPDATE', + DELETE = 'DELETE', + ALL = 'ALL', +} + @Injectable({ providedIn: 'root' }) export class AuthService extends BaseService { private http = inject(HttpClient); @@ -42,17 +50,33 @@ export class AuthService extends BaseService { hasPermission(permissionName: string, action: string): boolean { return ( - this.getActionsForPermission(permissionName).find((value) => value === 'ALL' || value === action) !== undefined + this.getActionsForPermission(permissionName).find((value) => value === Action.ALL || value === action) !== + undefined + ); + } + + hasResourceGroupPermission(permissionName: string, action: string, resourceGroupId: number): boolean { + return ( + this.restrictions() + .filter((entry) => entry.permission.name === permissionName) + .filter((entry) => entry.resourceGroupId === resourceGroupId || entry.resourceGroupId === null) + .map((entry) => entry.action) + .find((entry) => entry === Action.ALL || entry === action) !== undefined ); } - hasResourcePermission(permissionName: string, action: string, resourceType: string): boolean { + hasResourceTypePermission(permissionName: string, action: string, resourceTypeName: string): boolean { return ( this.restrictions() .filter((entry) => entry.permission.name === permissionName) - .filter((entry) => entry.resourceTypeName === resourceType || this.isDefaultType(entry, resourceType)) + .filter( + (entry) => + entry.resourceTypeName === resourceTypeName || + this.isDefaultType(entry, resourceTypeName) || + entry.resourceTypeName === null, + ) .map((entry) => entry.action) - .find((entry) => entry === 'ALL' || entry === action) !== undefined + .find((entry) => entry === Action.ALL || entry === action) !== undefined ); } @@ -67,6 +91,6 @@ export class AuthService extends BaseService { // usage example: actions.some(isAllowed("CREATE")) export function isAllowed(role: string) { return (action: string) => { - return action === 'ALL' || action === role; + return action === Action.ALL || action === role; }; } diff --git a/AMW_angular/io/src/app/resource/resource.ts b/AMW_angular/io/src/app/resource/resource.ts index 766f767bc..5d0b661a3 100644 --- a/AMW_angular/io/src/app/resource/resource.ts +++ b/AMW_angular/io/src/app/resource/resource.ts @@ -8,4 +8,6 @@ export interface Resource { defaultRelease: Release; releases: Release[]; defaultResourceId?: number; + resourceGroupId?: number; + resourceTypeId?: number; } diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html similarity index 58% rename from AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html rename to AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html index 505f1ed1d..48bbb36a5 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.html +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.html @@ -12,21 +12,16 @@ Please provide a resource-id to edit a resource. } @else { -
- Loaded resource: - {{ this.resource()?.name }} -
-
- -
+ + } diff --git a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.spec.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.spec.ts similarity index 74% rename from AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.spec.ts rename to AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.spec.ts index 0ff2f88fd..709bedd18 100644 --- a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.spec.ts +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.spec.ts @@ -1,14 +1,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { ResourceTypeEditPageComponent } from './resource-type-edit-page.component'; +import { ResourceEditComponent } from './resource-edit.component'; import { ActivatedRoute } from '@angular/router'; import { of, Subject } from 'rxjs'; import { RouterTestingModule } from '@angular/router/testing'; describe('ResourceEditPageComponent', () => { - let component: ResourceTypeEditPageComponent; - let fixture: ComponentFixture; + let component: ResourceEditComponent; + let fixture: ComponentFixture; const mockRoute: any = { queryParamMap: of() }; mockRoute.queryParamMap = new Subject>(); @@ -18,7 +18,7 @@ describe('ResourceEditPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ResourceTypeEditPageComponent, RouterTestingModule.withRoutes([])], + imports: [ResourceEditComponent, RouterTestingModule.withRoutes([])], providers: [ provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), @@ -26,7 +26,7 @@ describe('ResourceEditPageComponent', () => { ], }).compileComponents(); - fixture = TestBed.createComponent(ResourceTypeEditPageComponent); + fixture = TestBed.createComponent(ResourceEditComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts similarity index 91% rename from AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts rename to AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts index 990fa2c77..5a3167cf5 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.ts +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-edit.component.ts @@ -10,16 +10,15 @@ import { EntryAction, TileListEntry, TileListEntryOutput } from '../../shared/ti import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TileComponent } from '../../shared/tile/tile.component'; import { AuthService } from '../../auth/auth.service'; -import { ResourceType } from '../../resource/resource-type'; -import { ResourceTypesService } from '../../resource/resource-types.service'; +import { ResourceFunctionsListComponent } from './resource-functions/resource-functions-list.component'; @Component({ - selector: 'app-resource-edit-page', + selector: 'app-resource-edit', standalone: true, - imports: [LoadingIndicatorComponent, PageComponent, TileComponent], - templateUrl: './resource-edit-page.component.html', + imports: [LoadingIndicatorComponent, PageComponent, TileComponent, ResourceFunctionsListComponent], + templateUrl: './resource-edit.component.html', }) -export class ResourceEditPageComponent { +export class ResourceEditComponent { private authService = inject(AuthService); private modalService = inject(NgbModal); private resourceService = inject(ResourceService); diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html new file mode 100644 index 000000000..d440c8f9c --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.html @@ -0,0 +1,9 @@ + + diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.spec.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.spec.ts new file mode 100644 index 000000000..f139560c9 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InputSignal, signal } from '@angular/core'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ResourceFunctionsListComponent } from './resource-functions-list.component'; +import { Resource } from '../../../resource/resource'; + +describe('ResourceFunctionsComponent', () => { + let component: ResourceFunctionsListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResourceFunctionsListComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(ResourceFunctionsListComponent); + component = fixture.componentInstance; + component.resource = signal(null) as unknown as InputSignal; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.ts b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.ts new file mode 100644 index 000000000..734bdf601 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-edit/resource-functions/resource-functions-list.component.ts @@ -0,0 +1,156 @@ +import { Component, computed, inject, input, OnDestroy } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { LoadingIndicatorComponent } from '../../../shared/elements/loading-indicator.component'; +import { TileComponent } from '../../../shared/tile/tile.component'; + +import { EntryAction, TileListEntryOutput } from '../../../shared/tile/tile-list/tile-list.component'; +import { Action, AuthService } from '../../../auth/auth.service'; +import { Resource } from '../../../resource/resource'; +import { ResourceFunctionsService } from '../../resource-functions.service'; +import { ResourceFunction } from '../../resource-function'; +import { FunctionEditComponent } from '../../../settings/functions/function-edit.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +const RESOURCE_PERM = 'RESOURCE_AMWFUNCTION'; +const RESOURCETYPE_PERM = 'RESOURCETYPE_AMWFUNCTION'; + +@Component({ + selector: 'app-resource-functions-list', + standalone: true, + imports: [LoadingIndicatorComponent, TileComponent], + templateUrl: './resource-functions-list.component.html', +}) +export class ResourceFunctionsListComponent implements OnDestroy { + private authService = inject(AuthService); + private modalService = inject(NgbModal); + private functionsService = inject(ResourceFunctionsService); + private destroy$ = new Subject(); + + resource = input.required(); + contextId = input.required(); + functions = this.functionsService.functions; + + isLoading = computed(() => { + if (this.resource() != null) { + this.functionsService.setIdForResourceFunctionList(this.resource().id); + return false; + } + }); + + permissions = computed(() => { + if (this.authService.restrictions().length > 0 && this.resource()) { + return { + canShowInstanceFunctions: this.authService.hasPermission(RESOURCE_PERM, Action.READ), + canShowSuperTypeFunctions: this.authService.hasPermission(RESOURCETYPE_PERM, Action.READ), + canAdd: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.CREATE, this.resource().resourceGroupId), + canEdit: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.UPDATE, this.resource().resourceGroupId), + canDelete: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceGroupPermission(RESOURCE_PERM, Action.DELETE, this.resource().resourceGroupId), + }; + } else { + return { + canShowInstanceFunctions: false, + canShowSuperTypeFunctions: false, + canAdd: false, + canEdit: false, + canDelete: false, + }; + } + }); + + functionsData = computed(() => { + if (this.functions()?.length > 0) { + const [instance, resource] = this.splitFunctions(this.functions()); + const result = []; + if (this.permissions().canShowInstanceFunctions) { + result.push({ + title: 'Resource Instance Functions', + entries: instance, + canEdit: this.permissions().canEdit || this.permissions().canShowInstanceFunctions, // fixme old gui used the `Edit`-link also for only viewing a function + canDelete: this.permissions().canDelete, + }); + } + if (this.permissions().canShowSuperTypeFunctions) { + result.push({ + title: 'Resource Type Functions', + entries: resource, + canOverwrite: this.permissions().canEdit || this.permissions().canShowInstanceFunctions, // fixme old gui used the `Edit`-link also for only viewing a function + }); + } + return result; + } else return null; + }); + + ngOnDestroy(): void { + this.destroy$.next(undefined); + } + + add() { + const modalRef = this.modalService.open(FunctionEditComponent, { + size: 'xl', + }); + modalRef.componentInstance.function = { + id: null, + name: '', + content: '', + }; + modalRef.componentInstance.canManage = this.permissions().canEdit; + modalRef.componentInstance.saveFunction + .pipe(takeUntil(this.destroy$)) + .subscribe((functionData: ResourceFunction) => console.log(functionData)); + } + + doListAction($event: TileListEntryOutput) { + switch ($event.action) { + case EntryAction.edit: + this.editFunction($event.id); + return; + case EntryAction.delete: + this.deleteFunction($event.id); + return; + case EntryAction.overwrite: + this.overwriteFunction($event.id); + return; + } + } + + mapListEntries(functions: ResourceFunction[]) { + return functions.map((element) => ({ + name: + element.name + + (element.definedOnResourceType + ? ` (Defined on ${element.functionOriginResourceName})` + : element.isOverwritingFunction + ? ` (Overwrite function from ${element.overwrittenParentName})` + : ''), + description: element.miks.join(', '), + id: element.id, + })); + } + + splitFunctions(resourceFunctions: ResourceFunction[]) { + const [instance, resource] = [[], []]; + resourceFunctions.sort((a, b) => (a.name < b.name ? -1 : 1)); + resourceFunctions.forEach((element) => (element.definedOnResourceType ? resource : instance).push(element)); + return [this.mapListEntries(instance), this.mapListEntries(resource)]; + } + + private editFunction(id: number) { + this.modalService.open('This would open a modal to edit function with id:' + id); + } + + private deleteFunction(id: number) { + this.modalService.open('This would open a modal to delete function with id:' + id); + } + + private overwriteFunction(id: number) { + this.modalService.open('This would open a modal to overwrite function with id:' + id); + } +} diff --git a/AMW_angular/io/src/app/resources/resource-function.ts b/AMW_angular/io/src/app/resources/resource-function.ts new file mode 100644 index 000000000..07781a478 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-function.ts @@ -0,0 +1,10 @@ +export interface ResourceFunction { + id: number; + name: string; + miks: string[]; + definedOnResource: boolean; + definedOnResourceType: boolean; + isOverwritingFunction: boolean; + overwrittenParentName?: string; + functionOriginResourceName?: string; +} diff --git a/AMW_angular/io/src/app/resources/resource-functions.service.ts b/AMW_angular/io/src/app/resources/resource-functions.service.ts new file mode 100644 index 000000000..036b5acac --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-functions.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ResourceFunction } from './resource-function'; +import { catchError, shareReplay, switchMap } from 'rxjs/operators'; +import { Observable, Subject } from 'rxjs'; + +import { toSignal } from '@angular/core/rxjs-interop'; +import { BaseService } from '../base/base.service'; + +@Injectable({ providedIn: 'root' }) +export class ResourceFunctionsService extends BaseService { + private path = `${this.getBaseUrl()}/resources/functions`; + private functions$: Subject = new Subject(); + private functionsForType$: Subject = new Subject(); + + private functionById$: Observable = this.functions$.pipe( + switchMap((id: number) => this.getResourceFunctions(id)), + shareReplay(1), + ); + + private functionByTypeId$: Observable = this.functionsForType$.pipe( + switchMap((id: number) => this.getResourceTypeFunctions(id)), + shareReplay(1), + ); + + functions = toSignal(this.functionById$, { initialValue: [] }); + functionsForType = toSignal(this.functionByTypeId$, { initialValue: [] }); + + constructor(private http: HttpClient) { + super(); + } + + setIdForResourceFunctionList(id: number) { + this.functions$.next(id); + } + + setIdForResourceTypeFunctionList(id: number) { + this.functionsForType$.next(id); + } + + getResourceFunctions(id: number): Observable { + return this.http + .get(`${this.path}/resource/${id}`, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } + + getResourceTypeFunctions(id: number): Observable { + return this.http + .get(`${this.path}/resourceType/${id}`, { + headers: this.getHeaders(), + }) + .pipe(catchError(this.handleError)); + } +} diff --git a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.html b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html similarity index 58% rename from AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.html rename to AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html index 88e097446..2ed86e3fa 100644 --- a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.html +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.html @@ -12,21 +12,19 @@ Please provide a resourceType-id to edit a resource. } @else { -
- Loaded resourceType: - {{ this.resourceType()?.name }} -
-
- -
+ + } diff --git a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.spec.ts similarity index 77% rename from AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts rename to AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.spec.ts index 697fa9201..e36553b95 100644 --- a/AMW_angular/io/src/app/resources/resource-edit-page/resource-edit-page.component.spec.ts +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.spec.ts @@ -1,14 +1,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; -import { ResourceEditPageComponent } from './resource-edit-page.component'; +import { ResourceTypeEditComponent } from './resource-type-edit.component'; import { ActivatedRoute } from '@angular/router'; import { of, Subject } from 'rxjs'; import { RouterTestingModule } from '@angular/router/testing'; describe('ResourceEditPageComponent', () => { - let component: ResourceEditPageComponent; - let fixture: ComponentFixture; + let component: ResourceTypeEditComponent; + let fixture: ComponentFixture; const mockRoute: any = { queryParamMap: of() }; mockRoute.queryParamMap = new Subject>(); @@ -18,7 +18,7 @@ describe('ResourceEditPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ResourceEditPageComponent, RouterTestingModule.withRoutes([])], + imports: [ResourceTypeEditComponent, RouterTestingModule.withRoutes([])], providers: [ provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting(), @@ -26,7 +26,7 @@ describe('ResourceEditPageComponent', () => { ], }).compileComponents(); - fixture = TestBed.createComponent(ResourceEditPageComponent); + fixture = TestBed.createComponent(ResourceTypeEditComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.ts similarity index 91% rename from AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.ts rename to AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.ts index 2cff30638..f70d41cc1 100644 --- a/AMW_angular/io/src/app/resources/resource-type-edit-page/resource-type-edit-page.component.ts +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-edit.component.ts @@ -4,22 +4,21 @@ import { PageComponent } from '../../layout/page/page.component'; import { ActivatedRoute } from '@angular/router'; import { map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; -import { ResourceService } from '../../resource/resource.service'; -import { Resource } from '../../resource/resource'; import { EntryAction, TileListEntry, TileListEntryOutput } from '../../shared/tile/tile-list/tile-list.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TileComponent } from '../../shared/tile/tile.component'; import { AuthService } from '../../auth/auth.service'; import { ResourceType } from '../../resource/resource-type'; import { ResourceTypesService } from '../../resource/resource-types.service'; +import { ResourceTypeFunctionsListComponent } from './resource-type-functions/resource-type-functions-list.component'; @Component({ - selector: 'app-resource-type-edit-page', + selector: 'app-resource-type-edit', standalone: true, - imports: [LoadingIndicatorComponent, PageComponent, TileComponent], - templateUrl: './resource-type-edit-page.component.html', + imports: [LoadingIndicatorComponent, PageComponent, TileComponent, ResourceTypeFunctionsListComponent], + templateUrl: './resource-type-edit.component.html', }) -export class ResourceTypeEditPageComponent { +export class ResourceTypeEditComponent { private authService = inject(AuthService); private modalService = inject(NgbModal); private resourceTypeService = inject(ResourceTypesService); diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.html b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.html new file mode 100644 index 000000000..d440c8f9c --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.html @@ -0,0 +1,9 @@ + + diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.spec.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.spec.ts new file mode 100644 index 000000000..c1940c47d --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InputSignal, signal } from '@angular/core'; +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { ResourceTypeFunctionsListComponent } from './resource-type-functions-list.component'; +import { ResourceType } from '../../../resource/resource-type'; + +describe('ResourceFunctionsComponent', () => { + let component: ResourceTypeFunctionsListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResourceTypeFunctionsListComponent], + providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()], + }).compileComponents(); + + fixture = TestBed.createComponent(ResourceTypeFunctionsListComponent); + component = fixture.componentInstance; + component.resourceType = signal(null) as unknown as InputSignal; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.ts b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.ts new file mode 100644 index 000000000..0f201a524 --- /dev/null +++ b/AMW_angular/io/src/app/resources/resource-type-edit/resource-type-functions/resource-type-functions-list.component.ts @@ -0,0 +1,110 @@ +import { Component, computed, inject, input } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { LoadingIndicatorComponent } from '../../../shared/elements/loading-indicator.component'; +import { TileComponent } from '../../../shared/tile/tile.component'; + +import { EntryAction, TileListEntryOutput } from '../../../shared/tile/tile-list/tile-list.component'; +import { Action, AuthService } from '../../../auth/auth.service'; +import { ResourceFunctionsService } from '../../resource-functions.service'; +import { ResourceFunction } from '../../resource-function'; +import { ResourceType } from '../../../resource/resource-type'; + +const RESOURCETYPE_PERM = 'RESOURCETYPE_AMWFUNCTION'; + +@Component({ + selector: 'app-resource-type-functions-list', + standalone: true, + imports: [LoadingIndicatorComponent, TileComponent], + templateUrl: './resource-type-functions-list.component.html', +}) +export class ResourceTypeFunctionsListComponent { + private authService = inject(AuthService); + private modalService = inject(NgbModal); + private functionsService = inject(ResourceFunctionsService); + + resourceType = input.required(); + contextId = input.required(); + functions = this.functionsService.functionsForType; + + isLoading = computed(() => { + if (this.resourceType() != null) { + this.functionsService.setIdForResourceTypeFunctionList(this.resourceType().id); + return false; + } + }); + + permissions = computed(() => { + if (this.authService.restrictions().length > 0 && this.resourceType()) { + return { + canShowInstanceFunctions: this.authService.hasPermission(RESOURCETYPE_PERM, Action.READ), + canAdd: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.CREATE, this.resourceType().name), + canEdit: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.UPDATE, this.resourceType().name), + canDelete: + (this.contextId() === 1 || this.contextId === null) && + this.authService.hasResourceTypePermission(RESOURCETYPE_PERM, Action.DELETE, this.resourceType().name), + }; + } else { + return { + canShowInstanceFunctions: false, + canAdd: false, + canEdit: false, + canDelete: false, + }; + } + }); + + functionsData = computed(() => { + if (this.functions()?.length > 0) { + if (this.permissions().canShowInstanceFunctions) { + const entries = this.mapListEntries(this.functions()); + return [ + { + title: 'Type Functions', + entries: entries, + canEdit: this.permissions().canEdit || this.permissions().canShowInstanceFunctions, // fixme old gui used the `Edit`-link also for only viewing a function + canDelete: this.permissions().canDelete, + }, + ]; + } else return null; + } + }); + + add() { + this.modalService.open('This would open a modal to add something'); + } + + doListAction($event: TileListEntryOutput) { + switch ($event.action) { + case EntryAction.edit: + this.editFunction($event.id); + return; + case EntryAction.delete: + this.deleteFunction($event.id); + return; + case EntryAction.overwrite: + this.overwriteFunction($event.id); + return; + } + } + + mapListEntries(functions: ResourceFunction[]) { + return functions.map((element) => ({ name: element.name, description: element.miks.join(', '), id: element.id })); + } + + private editFunction(id: number) { + this.modalService.open('This would open a modal to edit function with id:' + id); + } + + private deleteFunction(id: number) { + this.modalService.open('This would open a modal to delete function with id:' + id); + } + + private overwriteFunction(id: number) { + this.modalService.open('This would open a modal to overwrite function with id:' + id); + } +} diff --git a/AMW_angular/io/src/app/resources/resources.route.ts b/AMW_angular/io/src/app/resources/resources.route.ts index f7d1cb488..8b9e59c43 100644 --- a/AMW_angular/io/src/app/resources/resources.route.ts +++ b/AMW_angular/io/src/app/resources/resources.route.ts @@ -1,9 +1,9 @@ +import { ResourceEditComponent } from './resource-edit/resource-edit.component'; import { ResourcesPageComponent } from './resources-page.component'; -import { ResourceEditPageComponent } from './resource-edit-page/resource-edit-page.component'; -import { ResourceTypeEditPageComponent } from './resource-type-edit-page/resource-type-edit-page.component'; +import { ResourceTypeEditComponent } from './resource-type-edit/resource-type-edit.component'; export const resourcesRoute = [ { path: 'resources', component: ResourcesPageComponent }, - { path: 'resource/edit', component: ResourceEditPageComponent }, - { path: 'resourceType/edit', component: ResourceTypeEditPageComponent }, + { path: 'resource/edit', component: ResourceEditComponent }, + { path: 'resourceType/edit', component: ResourceTypeEditComponent }, ]; diff --git a/AMW_angular/io/src/app/servers/servers-page.component.ts b/AMW_angular/io/src/app/servers/servers-page.component.ts index 2e7e99e2c..dbd0d38f9 100644 --- a/AMW_angular/io/src/app/servers/servers-page.component.ts +++ b/AMW_angular/io/src/app/servers/servers-page.component.ts @@ -65,7 +65,7 @@ export class ServersPageComponent { permissions = computed(() => { if (this.authService.restrictions().length > 0) { return { - canReadAppServer: this.authService.hasResourcePermission('RESOURCE', 'CREATE', 'APPLICATION'), + canReadAppServer: this.authService.hasResourceTypePermission('RESOURCE', 'CREATE', 'APPLICATION'), canReadResources: this.authService.hasPermission('RESOURCE', 'READ'), }; } else { diff --git a/AMW_angular/io/src/app/shared/tile/tile.component.ts b/AMW_angular/io/src/app/shared/tile/tile.component.ts index 625b7e3ac..80994d530 100644 --- a/AMW_angular/io/src/app/shared/tile/tile.component.ts +++ b/AMW_angular/io/src/app/shared/tile/tile.component.ts @@ -9,7 +9,7 @@ import { TileListComponent, TileListEntry, TileListEntryOutput } from './tile-li @Component({ selector: 'app-tile-component', template: ` -
+
@if (showBody()) { diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/GetFunctionUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/GetFunctionUseCase.java new file mode 100644 index 000000000..132f6f217 --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/GetFunctionUseCase.java @@ -0,0 +1,9 @@ +package ch.puzzle.itc.mobiliar.business.function.boundary; + +import ch.puzzle.itc.mobiliar.business.function.entity.AmwFunctionEntity; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; + +public interface GetFunctionUseCase { + + AmwFunctionEntity get(Integer id) throws NotFoundException; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/ListFunctionsUseCase.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/ListFunctionsUseCase.java new file mode 100644 index 000000000..ac286f19f --- /dev/null +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/boundary/ListFunctionsUseCase.java @@ -0,0 +1,12 @@ +package ch.puzzle.itc.mobiliar.business.function.boundary; + +import ch.puzzle.itc.mobiliar.business.function.entity.AmwFunctionEntity; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; + +import java.util.List; + +public interface ListFunctionsUseCase { + + List functionsForResource(Integer id) throws NotFoundException; + List functionsForResourceType(Integer id) throws NotFoundException; +} diff --git a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/control/FunctionService.java b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/control/FunctionService.java index 5a1c414a6..4cb70275b 100644 --- a/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/control/FunctionService.java +++ b/AMW_business/src/main/java/ch/puzzle/itc/mobiliar/business/function/control/FunctionService.java @@ -20,23 +20,38 @@ package ch.puzzle.itc.mobiliar.business.function.control; +import ch.puzzle.itc.mobiliar.business.function.boundary.GetFunctionUseCase; +import ch.puzzle.itc.mobiliar.business.function.boundary.ListFunctionsUseCase; import ch.puzzle.itc.mobiliar.business.function.entity.AmwFunctionEntity; import ch.puzzle.itc.mobiliar.business.property.entity.MikEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.control.ResourceRepository; +import ch.puzzle.itc.mobiliar.business.resourcegroup.control.ResourceTypeRepository; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceTypeEntity; +import ch.puzzle.itc.mobiliar.business.security.entity.Permission; +import ch.puzzle.itc.mobiliar.business.security.interceptor.HasPermission; import ch.puzzle.itc.mobiliar.business.utils.Identifiable; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; +import org.hibernate.Hibernate; import javax.inject.Inject; +import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; -public class FunctionService { +import static ch.puzzle.itc.mobiliar.business.security.entity.Action.READ; - @Inject - FunctionRepository functionRepository; +public class FunctionService implements Serializable, GetFunctionUseCase, ListFunctionsUseCase { - @Inject - ResourceRepository resourceRepository; + @Inject + FunctionRepository functionRepository; + + @Inject + ResourceRepository resourceRepository; + + @Inject + ResourceTypeRepository resourceTypeRepository; /** * Returns all (overwritable) functions, which are defined on all parent resource types of the given resource instance - except the functions which are already defined on the given resource instance. @@ -44,8 +59,8 @@ public class FunctionService { public List getAllOverwritableSupertypeFunctions(ResourceEntity resourceEntity) { Map allSuperTypeFunctions = getAllTypeAndSuperTypeFunctions(resourceEntity.getResourceType()); - for(AmwFunctionEntity overwrittenFunction : resourceEntity.getFunctions()){ - if (allSuperTypeFunctions.containsKey(overwrittenFunction.getName())){ + for (AmwFunctionEntity overwrittenFunction : resourceEntity.getFunctions()) { + if (allSuperTypeFunctions.containsKey(overwrittenFunction.getName())) { allSuperTypeFunctions.remove(overwrittenFunction.getName()); } } @@ -60,12 +75,12 @@ public List getAllOverwritableSupertypeFunctions(ResourceType Map allSuperTypeFunctions = new LinkedHashMap<>(); - if (!resourceTypeEntity.isRootResourceType()){ + if (!resourceTypeEntity.isRootResourceType()) { allSuperTypeFunctions = getAllTypeAndSuperTypeFunctions(resourceTypeEntity.getParentResourceType()); } - for(AmwFunctionEntity overwrittenFunction : resourceTypeEntity.getFunctions()){ - if (allSuperTypeFunctions.containsKey(overwrittenFunction.getName())){ + for (AmwFunctionEntity overwrittenFunction : resourceTypeEntity.getFunctions()) { + if (allSuperTypeFunctions.containsKey(overwrittenFunction.getName())) { allSuperTypeFunctions.remove(overwrittenFunction.getName()); } } @@ -73,9 +88,13 @@ public List getAllOverwritableSupertypeFunctions(ResourceType return new ArrayList<>(allSuperTypeFunctions.values()); } - private Map getAllTypeAndSuperTypeFunctions(ResourceTypeEntity resourceTypeEntity){ + private Map getAllTypeAndSuperTypeFunctions(ResourceTypeEntity resourceTypeEntity) { Map superTypeFunctions = new LinkedHashMap<>(); if (resourceTypeEntity != null) { + if (!Hibernate.isInitialized(resourceTypeEntity.getFunctions())) { + resourceTypeEntity = resourceTypeRepository.loadWithFunctionsAndMiksForId(resourceTypeEntity.getId()); + } + for (AmwFunctionEntity function : resourceTypeEntity.getFunctions()) { AmwFunctionEntity functionWithMik = functionRepository.getFunctionByIdWithMiksAndParentChildFunctions(function.getId()); superTypeFunctions.put(function.getName(), functionWithMik); @@ -92,53 +111,55 @@ private Map getAllTypeAndSuperTypeFunctions(ResourceT return superTypeFunctions; } - /** - * Get all relevant functions for the given resource: - *
    - *
  • All functions of the resource
  • - *
  • Functions of the parent resourceTypes if not overwritten by the resource itself
  • - *
- * @param resource - * @return a list of AmwFunctions - */ - public List getAllFunctionsForResource(ResourceEntity resource) { - Objects.requireNonNull(resource, "Resource Entity must not be null"); - - ResourceEntity resourceWithFctAndMiks = resourceRepository.loadWithFunctionsAndMiksForId(resource.getId()); - List allFunctions = new ArrayList<>(resourceWithFctAndMiks.getFunctions()); - allFunctions.addAll(getAllOverwritableSupertypeFunctions(resourceWithFctAndMiks)); - - return allFunctions; - } - - /** - * Find the function for the given mik - * @param functions - * @param mik - * @return AmwFunctionEntity - */ - public AmwFunctionEntity getAMWFunctionForMIK(List functions, String mik) { - for (AmwFunctionEntity function : functions) { - for (String mikName : function.getMikNames()) { - if (mikName.equals(mik)) { - return function; - } - } - } - return null; - } - - public void saveFunctionWithMiks(AmwFunctionEntity amwFunction, Set functionMikNames) { - if (amwFunction != null) { - Set miks = new HashSet<>(); - for (String mikName : functionMikNames) { - miks.add(new MikEntity(mikName, amwFunction)); - } - - amwFunction.setMiks(miks); - functionRepository.persistOrMergeFunction(amwFunction); - } - } + /** + * Get all relevant functions for the given resource: + *
    + *
  • All functions of the resource
  • + *
  • Functions of the parent resourceTypes if not overwritten by the resource itself
  • + *
+ * + * @param resource + * @return a list of AmwFunctions + */ + public List getAllFunctionsForResource(ResourceEntity resource) { + Objects.requireNonNull(resource, "Resource Entity must not be null"); + + ResourceEntity resourceWithFctAndMiks = resourceRepository.loadWithFunctionsAndMiksForId(resource.getId()); + List allFunctions = new ArrayList<>(resourceWithFctAndMiks.getFunctions()); + allFunctions.addAll(getAllOverwritableSupertypeFunctions(resourceWithFctAndMiks)); + + return allFunctions; + } + + /** + * Find the function for the given mik + * + * @param functions + * @param mik + * @return AmwFunctionEntity + */ + public AmwFunctionEntity getAMWFunctionForMIK(List functions, String mik) { + for (AmwFunctionEntity function : functions) { + for (String mikName : function.getMikNames()) { + if (mikName.equals(mik)) { + return function; + } + } + } + return null; + } + + public void saveFunctionWithMiks(AmwFunctionEntity amwFunction, Set functionMikNames) { + if (amwFunction != null) { + Set miks = new HashSet<>(); + for (String mikName : functionMikNames) { + miks.add(new MikEntity(mikName, amwFunction)); + } + + amwFunction.setMiks(miks); + functionRepository.persistOrMergeFunction(amwFunction); + } + } /** * Find all functions within the namespace (scope of function name uniques: RootResourceType -> subResourceType -> ResourceInstance) for given name @@ -164,7 +185,7 @@ private List findFirstFunctionDefinedOnTypeAndSuperResourceTy List allFunctions = new ArrayList<>(); Map allSuperTypeFunctions = getAllTypeAndSuperTypeFunctions(resourceType); - if (allSuperTypeFunctions.containsKey(name)){ + if (allSuperTypeFunctions.containsKey(name)) { allFunctions.add(allSuperTypeFunctions.get(name)); } @@ -187,7 +208,7 @@ private List findFirstFunctionDefinedOnSubResourceTypesOrReso List allFunctions = new ArrayList<>(); Map allSubTypesAndResourcesFunctions = getAllSubTypeAndResourceFunctions(resourceType); - if (allSubTypesAndResourcesFunctions.containsKey(name)){ + if (allSubTypesAndResourcesFunctions.containsKey(name)) { allFunctions.add(allSubTypesAndResourcesFunctions.get(name)); } @@ -205,7 +226,7 @@ private List findAllResourceFunctionsByName(ResourceEntity re } - private Map getAllSubTypeAndResourceFunctions(ResourceTypeEntity resourceTypeEntity){ + private Map getAllSubTypeAndResourceFunctions(ResourceTypeEntity resourceTypeEntity) { Map subTypeFunctions = new LinkedHashMap<>(); @@ -232,31 +253,31 @@ public AmwFunctionEntity overwriteResourceFunction(String functionBody, AmwFunct return overwritingFunction; } - public AmwFunctionEntity overwriteResourceTypeFunction(String functionBody, AmwFunctionEntity functionToOverwrite, ResourceTypeEntity resourceType) { - if (resourceType.isRootResourceType() || isAlreadyOverwrittenInResourceType(functionToOverwrite, resourceType)){ + public AmwFunctionEntity overwriteResourceTypeFunction(String functionBody, AmwFunctionEntity functionToOverwrite, ResourceTypeEntity resourceType) { + if (resourceType.isRootResourceType() || isAlreadyOverwrittenInResourceType(functionToOverwrite, resourceType)) { throw new RuntimeException("Can not overwrite resource type function!"); } AmwFunctionEntity overwritingFunction = null; if (functionToOverwrite.isOverwrittenBySubTypeOrResourceFunction()) { - for (AmwFunctionEntity oldOverwritingFunction : functionToOverwrite.getOverwritingChildFunction()) { - if (isOverwrittenInSubTypeOrResource(resourceType, oldOverwritingFunction) && hasSameParentResourceType(resourceType, oldOverwritingFunction)) { + for (AmwFunctionEntity oldOverwritingFunction : functionToOverwrite.getOverwritingChildFunction()) { + if (isOverwrittenInSubTypeOrResource(resourceType, oldOverwritingFunction) && hasSameParentResourceType(resourceType, oldOverwritingFunction)) { overwritingFunction = replaceOverwriting(functionBody, functionToOverwrite, oldOverwritingFunction); break; - } - } + } + } - } + } if (overwritingFunction == null) { overwritingFunction = overwriteFunction(functionBody, functionToOverwrite); } overwritingFunction.setResourceType(resourceType); return overwritingFunction; - } + } private boolean isAlreadyOverwrittenInResourceType(AmwFunctionEntity functionToOverwrite, ResourceTypeEntity resourceType) { - for (AmwFunctionEntity overwritingFunction : functionToOverwrite.getOverwritingChildFunction()){ - if (overwritingFunction.isDefinedOnResourceType() && overwritingFunction.getResourceType().equals(resourceType)){ + for (AmwFunctionEntity overwritingFunction : functionToOverwrite.getOverwritingChildFunction()) { + if (overwritingFunction.isDefinedOnResourceType() && overwritingFunction.getResourceType().equals(resourceType)) { return true; } } @@ -268,10 +289,10 @@ private boolean isOverwrittenInSubTypeOrResource(ResourceTypeEntity resourceType List allSubResourceTypes = getAllSubResourceTypes(resourceType); - if (oldOverwritingFunction.isDefinedOnResourceType()){ - isSubType = allSubResourceTypes.contains(oldOverwritingFunction.getResourceType()); + if (oldOverwritingFunction.isDefinedOnResourceType()) { + isSubType = allSubResourceTypes.contains(oldOverwritingFunction.getResourceType()); } - if (oldOverwritingFunction.isDefinedOnResource()){ + if (oldOverwritingFunction.isDefinedOnResource()) { ResourceTypeEntity resourceTypeOfFunctionResource = oldOverwritingFunction.getResource().getResourceType(); isSubType = allSubResourceTypes.contains(resourceTypeOfFunctionResource) || resourceType.equals(resourceTypeOfFunctionResource); } @@ -279,10 +300,10 @@ private boolean isOverwrittenInSubTypeOrResource(ResourceTypeEntity resourceType return isSubType; } - private List getAllParentResourceTypes(ResourceTypeEntity resourceType){ + private List getAllParentResourceTypes(ResourceTypeEntity resourceType) { List allParentResourceTypes = new ArrayList<>(); - if (!resourceType.isRootResourceType()){ + if (!resourceType.isRootResourceType()) { allParentResourceTypes.add(resourceType.getParentResourceType()); allParentResourceTypes.addAll(getAllParentResourceTypes(resourceType.getParentResourceType())); } @@ -290,10 +311,10 @@ private List getAllParentResourceTypes(ResourceTypeEntity re return allParentResourceTypes; } - private List getAllSubResourceTypes(ResourceTypeEntity resourceType){ + private List getAllSubResourceTypes(ResourceTypeEntity resourceType) { List allSubResourceTypes = new ArrayList<>(); - for (ResourceTypeEntity subResourceType : resourceType.getChildrenResourceTypes()){ + for (ResourceTypeEntity subResourceType : resourceType.getChildrenResourceTypes()) { allSubResourceTypes.add(subResourceType); allSubResourceTypes.addAll(getAllSubResourceTypes(subResourceType)); } @@ -323,23 +344,62 @@ private boolean hasSameParentResourceType(ResourceTypeEntity resourceType, AmwFu } - private AmwFunctionEntity overwriteFunction(String functionBody, AmwFunctionEntity functionToOverwrite) { - AmwFunctionEntity overwritingFunction = new AmwFunctionEntity(); - overwritingFunction.setName(functionToOverwrite.getName()); - overwritingFunction.setImplementation(functionBody); - overwritingFunction.overwrite(functionToOverwrite); - saveFunctionWithMiks(overwritingFunction, functionToOverwrite.getMikNames()); - return overwritingFunction; - } - - /** - * Delete function and removes dependencies in overridden parent functions - */ - public void deleteFunction(AmwFunctionEntity functionToDelete) { - if (functionToDelete.isOverwritingResourceTypeFunction()) { - functionToDelete.resetOverwriting(); - } - - functionRepository.remove(functionToDelete); - } + private AmwFunctionEntity overwriteFunction(String functionBody, AmwFunctionEntity functionToOverwrite) { + AmwFunctionEntity overwritingFunction = new AmwFunctionEntity(); + overwritingFunction.setName(functionToOverwrite.getName()); + overwritingFunction.setImplementation(functionBody); + overwritingFunction.overwrite(functionToOverwrite); + saveFunctionWithMiks(overwritingFunction, functionToOverwrite.getMikNames()); + return overwritingFunction; + } + + /** + * Delete function and removes dependencies in overridden parent functions + */ + public void deleteFunction(AmwFunctionEntity functionToDelete) { + if (functionToDelete.isOverwritingResourceTypeFunction()) { + functionToDelete.resetOverwriting(); + } + + functionRepository.remove(functionToDelete); + } + + @Override + @HasPermission(permission = Permission.RESOURCE_AMWFUNCTION, action = READ) + public AmwFunctionEntity get(Integer id) throws NotFoundException { + AmwFunctionEntity entity = functionRepository.getFunctionByIdWithMiksAndParentChildFunctions(id); + if (entity != null) { + return entity; + } else { + throw new NotFoundException("Function not found."); + } + } + + @Override + @HasPermission(permission = Permission.RESOURCE_AMWFUNCTION, action = READ) + public List functionsForResource(Integer id) throws NotFoundException { + + ResourceEntity resourceEntity = resourceRepository.loadWithFunctionsAndMiksForId(id); + if (resourceEntity != null) { + List instanceFuncs = new ArrayList<>(resourceEntity.getFunctions()); + List superFuncs = getAllOverwritableSupertypeFunctions(resourceEntity); + return Stream.of(instanceFuncs, superFuncs).flatMap(Collection::stream).collect(Collectors.toList()); + } else { + throw new NotFoundException("Resource not found."); + } + } + + @Override + @HasPermission(permission = Permission.RESOURCE_AMWFUNCTION, action = READ) + public List functionsForResourceType(Integer id) throws NotFoundException { + ResourceTypeEntity resourceTypeEntity = resourceTypeRepository.loadWithFunctionsAndMiksForId(id); + if (resourceTypeEntity != null) { + List instanceFuncs = new ArrayList<>(resourceTypeEntity.getFunctions()); + List superFuncs = getAllOverwritableSupertypeFunctions(resourceTypeEntity); + + return Stream.of(instanceFuncs, superFuncs).flatMap(Collection::stream).collect(Collectors.toList()); + } else { + throw new NotFoundException("Resource not found."); + } + } } diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java index 0fd601d5c..f61ae696b 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/RESTApplication.java @@ -79,6 +79,7 @@ private void addRestResourceClasses(Set> resources) { resources.add(PropertyTypesRest.class); resources.add(FunctionsRest.class); resources.add(ServersRest.class); + resources.add(ResourceFunctionsRest.class); // writers resources.add(DeploymentDtoCsvBodyWriter.class); diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java index fe38f3f92..598370c48 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/apps/AppsRest.java @@ -1,12 +1,9 @@ package ch.mobi.itc.mobiliar.rest.apps; import ch.mobi.itc.mobiliar.rest.dtos.AppAppServerDTO; -import ch.mobi.itc.mobiliar.rest.dtos.AppDTO; import ch.mobi.itc.mobiliar.rest.dtos.AppServerDTO; import ch.puzzle.itc.mobiliar.business.apps.boundary.*; -import ch.puzzle.itc.mobiliar.business.releasing.boundary.ReleaseLocator; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceWithRelations; -import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; import ch.puzzle.itc.mobiliar.common.util.Tuple; import io.swagger.annotations.Api; diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java index e6cd839ed..48f9934de 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/AppAppServerDTO.java @@ -1,7 +1,5 @@ package ch.mobi.itc.mobiliar.rest.dtos; -import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; -import io.swagger.models.auth.In; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/FunctionDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/FunctionDTO.java new file mode 100644 index 000000000..452733a65 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/FunctionDTO.java @@ -0,0 +1,44 @@ +package ch.mobi.itc.mobiliar.rest.dtos; + + +import ch.puzzle.itc.mobiliar.business.function.entity.AmwFunctionEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.Set; + +@XmlRootElement(name = "function") +@XmlAccessorType(XmlAccessType.FIELD) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FunctionDTO { + + Integer id; + String name; + String implementation; + Set miks; + Boolean definedOnResource; + Boolean definedOnResourceType; + Boolean isOverwritingFunction; + String overwrittenParentName; + String functionOriginResourceName; + + + + public FunctionDTO(AmwFunctionEntity entity) { + this.id = entity.getId(); + this.name = entity.getName(); + this.implementation = entity.getImplementation(); + this.definedOnResource = entity.isDefinedOnResource(); + this.definedOnResourceType = entity.isDefinedOnResourceType(); + this.miks = entity.getMikNames(); + this.isOverwritingFunction = entity.isOverwritingResourceTypeFunction(); + this.overwrittenParentName = entity.getOverwrittenFunctionResourceTypeName(); + this.functionOriginResourceName = entity.getResourceType() != null ? entity.getResourceType().getName() : ""; + } +} diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ResourceDTO.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ResourceDTO.java index c7af26e8f..34ba90e19 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ResourceDTO.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/dtos/ResourceDTO.java @@ -30,6 +30,7 @@ import ch.puzzle.itc.mobiliar.business.configurationtag.entity.ResourceTagEntity; import ch.puzzle.itc.mobiliar.business.releasing.entity.ReleaseEntity; import ch.puzzle.itc.mobiliar.business.resourcegroup.entity.ResourceEntity; +import io.swagger.models.auth.In; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -42,6 +43,8 @@ public class ResourceDTO { private Integer id; // Release id private String name; + private Integer resourceTypeId; + private Integer resourceGroupId; private String release; private List relations; @@ -84,8 +87,11 @@ public ResourceDTO(ResourceEntity resource, List relations) public ResourceDTO(ResourceEntity resource) { this.id = resource.getId(); + this.resourceGroupId = resource.getResourceGroup().getId(); + this.resourceTypeId = resource.getResourceType().getId(); this.name = resource.getName(); this.release = resource.getRelease().getName(); + } } diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/functions/FunctionsRest.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/functions/FunctionsRest.java index 419e47780..65d343f0c 100644 --- a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/functions/FunctionsRest.java +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/functions/FunctionsRest.java @@ -28,21 +28,21 @@ public class FunctionsRest { private GlobalFunctionsBoundary globalFunctionsBoundary; @GET - @ApiOperation(value = "Get all functions") + @ApiOperation(value = "Get all global functions") public List getAllFunctions() { return globalFunctionsBoundary.getAllGlobalFunctions(); } @GET @Path("/{id}") - @ApiOperation(value = "Get a specific function") + @ApiOperation(value = "Get a global function by id") public Response getFunctionById(@PathParam("id") int id) throws NotFoundException { GlobalFunctionEntity function = globalFunctionsBoundary.getFunctionById(id); return Response.ok(function).build(); } @POST - @ApiOperation(value = "Add new function") + @ApiOperation(value = "Add new global function") public Response addNewFunction(GlobalFunctionEntity request) { try { globalFunctionsBoundary.saveGlobalFunction(request); @@ -54,7 +54,7 @@ public Response addNewFunction(GlobalFunctionEntity request) { } @PUT - @ApiOperation(value = "Modify existing function") + @ApiOperation(value = "Modify existing global function") public Response modifyFunction(GlobalFunctionEntity request) { if (request.getId() == null || !globalFunctionsBoundary.isExistingId(request.getId())) { return Response.status(Response.Status.BAD_REQUEST).entity(Collections.singletonMap("message", "Only existing functions can be modified")) @@ -71,7 +71,7 @@ public Response modifyFunction(GlobalFunctionEntity request) { @DELETE @Path("/{id}") - @ApiOperation(value = "Remove a function") + @ApiOperation(value = "Remove a global function") public Response deleteFunction(@PathParam("id") int id) throws NotFoundException { globalFunctionsBoundary.deleteGlobalFunction(id); return Response.status(Response.Status.NO_CONTENT).build(); @@ -79,7 +79,7 @@ public Response deleteFunction(@PathParam("id") int id) throws NotFoundException @GET @Path("/{id}/revisions") - @ApiOperation(value = "Get all revisions of a specific function") + @ApiOperation(value = "Get all revisions of a specific global function") public Response getFunctionRevisions(@PathParam("id") int id) throws NotFoundException { List revisions = globalFunctionsBoundary.getFunctionRevisions(id); if (revisions.isEmpty()) { @@ -90,7 +90,7 @@ public Response getFunctionRevisions(@PathParam("id") int id) throws NotFoundExc @GET @Path("/{id}/revisions/{revisionId}") - @ApiOperation(value = "Get a specific revision of a function") + @ApiOperation(value = "Get a specific revision of a global function") public Response getFunctionByIdAndRevision(@PathParam("id") int id, @PathParam("revisionId") int revisionId) throws NotFoundException { GlobalFunctionEntity function = globalFunctionsBoundary.getFunctionByIdAndRevision(id, revisionId); return Response.ok(function).build(); diff --git a/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceFunctionsRest.java b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceFunctionsRest.java new file mode 100644 index 000000000..6fa851d62 --- /dev/null +++ b/AMW_rest/src/main/java/ch/mobi/itc/mobiliar/rest/resources/ResourceFunctionsRest.java @@ -0,0 +1,74 @@ +package ch.mobi.itc.mobiliar.rest.resources; + +import ch.mobi.itc.mobiliar.rest.dtos.FunctionDTO; +import ch.puzzle.itc.mobiliar.business.function.boundary.GetFunctionUseCase; +import ch.puzzle.itc.mobiliar.business.function.boundary.ListFunctionsUseCase; +import ch.puzzle.itc.mobiliar.business.function.entity.AmwFunctionEntity; +import ch.puzzle.itc.mobiliar.common.exception.NotFoundException; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.util.ArrayList; +import java.util.List; + +import static javax.ws.rs.core.Response.Status.OK; + +@RequestScoped +@Path("/resources/functions") +@Api(value = "/resources/functions/", description = "Resource functions") +public class ResourceFunctionsRest { + + @Inject + private GetFunctionUseCase getFunctionUseCase; + + @Inject + private ListFunctionsUseCase listFunctionsUseCase; + + @GET + @Path("/{id : \\d+}") + @ApiOperation(value = "Get a resource function by id") + @Produces(MediaType.APPLICATION_JSON) + public Response getFunction(@ApiParam("Function ID") @PathParam("id") Integer id) throws NotFoundException { + AmwFunctionEntity entity = getFunctionUseCase.get(id); + return Response.status(OK).entity(new FunctionDTO(entity)).build(); + } + + + @GET + @Path("/resource/{id : \\d+}") + @ApiOperation(value = "Get all functions for a specific resource") + @Produces(MediaType.APPLICATION_JSON) + public Response getResourceFunctions(@ApiParam("Resource ID") @PathParam("id") Integer resourceId) throws NotFoundException { + List entity = listFunctionsUseCase.functionsForResource(resourceId); + return Response.status(OK).entity(functionsToResponse(entity)).build(); + } + + @GET + @Path("/resourceType/{id : \\d+}") + @ApiOperation(value = "Get all functions for a specific resourceType") + @Produces(MediaType.APPLICATION_JSON) + public Response getResourceTypeFunctions(@ApiParam("ResourceType ID") @PathParam("id") Integer resourceTypeId) throws NotFoundException { + List entity = listFunctionsUseCase.functionsForResourceType(resourceTypeId); + return Response.status(OK).entity(functionsToResponse(entity)).build(); + } + + + private Object functionsToResponse(List entity) { + List dtos = new ArrayList<>(entity.size()); + for (AmwFunctionEntity entityItem : entity) { + dtos.add(new FunctionDTO(entityItem)); + } + return dtos; + } + + + + +}