Skip to content

Commit

Permalink
Use observable to search for IAM roles and display loading it (#240)
Browse files Browse the repository at this point in the history
* fix: use observable to search for iam roles and display loading

* fix: code smells

* fix: added tests for translations
  • Loading branch information
HenryT-CG authored Oct 21, 2024
1 parent a26d63d commit c0a9fd4
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 76 deletions.
13 changes: 13 additions & 0 deletions src/app/permission/app-detail/app-detail.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,4 +1074,17 @@ describe('AppDetailComponent', () => {
fixture.detectChanges()
expect(component.dateFormat).toEqual('dd.MM.yyyy HH:mm')
})

describe('Test translations', () => {
it('should translate quick filter items', () => {
component.prepareQuickFilterItems()

let items: any = []
component.quickFilterItems$!.subscribe((data) => (items = data))

items[0].value

expect(items[0].value).toEqual('ALL')
})
})
})
72 changes: 38 additions & 34 deletions src/app/permission/app-detail/app-detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
@ViewChild('sortIconProduct') sortIconProduct: ElementRef | undefined

// data
private pageSize = 1000
private readonly pageSize = 1000
public urlParamAppId: string | null
public urlParamAppType: string | undefined
public currentApp: App = { appId: 'dummy', appType: 'PRODUCT', isProduct: true } as App
Expand Down Expand Up @@ -120,16 +120,16 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public showRoleTools = false

constructor(
private appApi: ApplicationAPIService,
private assApi: AssignmentAPIService,
private permApi: PermissionAPIService,
private roleApi: RoleAPIService,
private workspaceApi: WorkspaceAPIService,
private route: ActivatedRoute,
private location: Location,
private translate: TranslateService,
private msgService: PortalMessageService,
private userService: UserService
private readonly appApi: ApplicationAPIService,
private readonly assApi: AssignmentAPIService,
private readonly permApi: PermissionAPIService,
private readonly roleApi: RoleAPIService,
private readonly workspaceApi: WorkspaceAPIService,
private readonly route: ActivatedRoute,
private readonly location: Location,
private readonly translate: TranslateService,
private readonly msgService: PortalMessageService,
private readonly userService: UserService
) {
this.urlParamAppId = this.route.snapshot.paramMap.get('appId')
this.urlParamAppType = this.route.snapshot.paramMap.get('appType')?.toUpperCase()
Expand Down Expand Up @@ -159,7 +159,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
}

public ngOnInit(): void {
this.preparequickFilterItems()
this.prepareQuickFilterItems()
this.prepareActionButtons()
this.loadData()
}
Expand All @@ -168,7 +168,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.destroy$.complete()
}

private preparequickFilterItems(): void {
public prepareQuickFilterItems(): void {
this.quickFilterItems$ = this.translate
.get([
'DIALOG.DETAIL.QUICK_FILTER.ALL',
Expand Down Expand Up @@ -756,6 +756,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
* 3.2 If currentApp is WORKSPACE then all product are used
*/
public onGrantAllPermissions(ev: MouseEvent, role: Role): void {
const productNames = this.prepareProductListForBulkOperation()
const response: any = {
next: () => {
this.msgService.success({ summaryKey: 'PERMISSION.ASSIGNMENTS.GRANT_ALL_SUCCESS' })
Expand All @@ -767,20 +768,21 @@ export class AppDetailComponent implements OnInit, OnDestroy {
}
}
if (this.filterAppValue) {
this.assApi
.grantRoleApplicationAssignments({
roleId: role.id,
createRoleApplicationAssignmentRequest: {
appId: this.filterAppValue,
productName: this.prepareProductListForBulkOperation()[0]
}
} as GrantRoleApplicationAssignmentsRequestParams)
.subscribe(response)
if (productNames.length === 1)
this.assApi
.grantRoleApplicationAssignments({
roleId: role.id,
createRoleApplicationAssignmentRequest: {
appId: this.filterAppValue,
productName: productNames[0]
}
} as GrantRoleApplicationAssignmentsRequestParams)
.subscribe(response)
} else {
this.assApi
.grantRoleProductsAssignments({
roleId: role.id,
createRoleProductsAssignmentRequest: { productNames: this.prepareProductListForBulkOperation() }
createRoleProductsAssignmentRequest: { productNames: productNames }
} as GrantRoleProductsAssignmentsRequestParams)
.subscribe(response)
}
Expand All @@ -790,6 +792,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
... see GRANT description above
*/
public onRevokeAllPermissions(ev: MouseEvent, role: Role): void {
const productNames = this.prepareProductListForBulkOperation()
const response: any = {
next: () => {
this.msgService.success({ summaryKey: 'PERMISSION.ASSIGNMENTS.REVOKE_ALL_SUCCESS' })
Expand All @@ -801,20 +804,21 @@ export class AppDetailComponent implements OnInit, OnDestroy {
}
}
if (this.filterAppValue) {
this.assApi
.revokeRoleApplicationAssignments({
roleId: role.id,
revokeRoleApplicationAssignmentRequest: {
appId: this.filterAppValue,
productName: this.prepareProductListForBulkOperation()[0]
}
} as RevokeRoleApplicationAssignmentsRequestParams)
.subscribe(response)
if (productNames.length === 1)
this.assApi
.revokeRoleApplicationAssignments({
roleId: role.id,
revokeRoleApplicationAssignmentRequest: {
appId: this.filterAppValue,
productName: productNames[0]
}
} as RevokeRoleApplicationAssignmentsRequestParams)
.subscribe(response)
} else {
this.assApi
.revokeRoleProductsAssignments({
roleId: role.id,
revokeRoleProductsAssignmentRequest: { productNames: this.prepareProductListForBulkOperation() }
revokeRoleProductsAssignmentRequest: { productNames: productNames }
} as RevokeRoleProductsAssignmentsRequestParams)
.subscribe(response)
}
Expand All @@ -825,7 +829,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
// case 1: APP filter value =>
if (this.filterAppValue) {
const apps = this.productApps.filter((p) => p.appId === this.filterAppValue)
apps.length === 1 ? pList.push(apps[0].productName ?? '') : undefined
if (apps.length === 1) pList.push(apps[0].productName ?? '')
// case 2: PRODUCT
} else if (this.currentApp.isProduct) pList.push(this.currentApp.productName ?? '')
// case 3: WORKSPACE
Expand Down
24 changes: 24 additions & 0 deletions src/app/permission/app-search/app-search.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,4 +673,28 @@ describe('AppSearchComponent', () => {

expect(component.displayExportDialog).toBeTrue()
})

describe('Test translations', () => {
it('should translate quick filter items', () => {
component.prepareQuickFilterItems()

let items: any = []
component.quickFilterItems$!.subscribe((data) => (items = data))

items[0].value

expect(items[0].value).toEqual('ALL')
})

it('should translate app type items', () => {
component.prepareAppTypeItems()

let items: any = []
component.appTypeItems$!.subscribe((data) => (items = data))

items[0].value

expect(items[0].value).toEqual('ALL')
})
})
})
18 changes: 9 additions & 9 deletions src/app/permission/app-search/app-search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ export class AppSearchComponent implements OnInit, OnDestroy {
@ViewChild(DataView) dv: DataView | undefined

constructor(
private appApi: ApplicationAPIService,
private assgnmtApi: AssignmentAPIService,
private route: ActivatedRoute,
private router: Router,
private translate: TranslateService,
private msgService: PortalMessageService,
private workspaceApi: WorkspaceAPIService
private readonly appApi: ApplicationAPIService,
private readonly assgnmtApi: AssignmentAPIService,
private readonly route: ActivatedRoute,
private readonly router: Router,
private readonly translate: TranslateService,
private readonly msgService: PortalMessageService,
private readonly workspaceApi: WorkspaceAPIService
) {
// search criteria
this.appSearchCriteriaGroup = new FormGroup<AppSearchCriteria>({
Expand Down Expand Up @@ -253,7 +253,7 @@ export class AppSearchComponent implements OnInit, OnDestroy {
/**
* Dialog preparation
*/
private prepareAppTypeItems(): void {
public prepareAppTypeItems(): void {
this.appTypeItems$ = this.translate
.get([
'DIALOG.SEARCH.FILTER.ALL',
Expand All @@ -272,7 +272,7 @@ export class AppSearchComponent implements OnInit, OnDestroy {
})
)
}
private prepareQuickFilterItems(): void {
public prepareQuickFilterItems(): void {
this.quickFilterItems$ = this.translate
.get([
'DIALOG.SEARCH.QUICK_FILTER.ALL',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export class PermissionDetailComponent implements OnChanges {
public formGroup: FormGroup

constructor(
private permApi: PermissionAPIService,
private translate: TranslateService,
private msgService: PortalMessageService,
private userService: UserService
private readonly permApi: PermissionAPIService,
private readonly translate: TranslateService,
private readonly msgService: PortalMessageService,
private readonly userService: UserService
) {
if (userService.hasPermission('PERMISSION#EDIT')) this.myPermissions.push('PERMISSION#EDIT')
if (userService.hasPermission('PERMISSION#DELETE')) this.myPermissions.push('PERMISSION#DELETE')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'
import FileSaver from 'file-saver'

import { PortalMessageService } from '@onecx/angular-integration-interface'
import { getCurrentDateTime } from 'src/app/shared/utils'

import { AssignmentAPIService } from 'src/app/shared/generated'
import { getCurrentDateTime } from 'src/app/shared/utils'

@Component({
selector: 'app-permission-export',
Expand All @@ -15,11 +16,12 @@ export class PermissionExportComponent {
@Input() displayExportDialog = false
@Input() listedProductsHeader = ''
@Output() displayExportDialogChange = new EventEmitter<boolean>()

public selectedProductNames: string[] = []

constructor(
private assgnmtApi: AssignmentAPIService,
private msgService: PortalMessageService
private readonly assgnmtApi: AssignmentAPIService,
private readonly msgService: PortalMessageService
) {}

public onExportConfirmation(): void {
Expand All @@ -44,6 +46,7 @@ export class PermissionExportComponent {
this.displayExportDialogChange.emit(false)
}
}

public onCloseExportDialog(): void {
this.displayExportDialogChange.emit(false)
this.selectedProductNames = []
Expand Down
32 changes: 30 additions & 2 deletions src/app/permission/role-detail/role-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,26 @@
[draggable]="true"
[resizable]="true"
[dismissableMask]="true"
*ngIf="(iamRoles$ | async)?.sort(sortRoleByName) ?? [] as roles"
>
<div class="flex flex-column row-gap-1">
<p-message
*ngIf="loadingExceptionKey"
id="apm_iam_roles_data_access_issue"
severity="error"
[text]="loadingExceptionKey | translate"
></p-message>
<p-message
*ngIf="loading"
id="apm_iam_roles_loading"
severity="info"
[text]="'ACTIONS.LOADING' | translate"
></p-message>

<div *ngIf="!loading && !loadingExceptionKey" class="flex flex-column row-gap-1">
<div>{{ 'ROLE.IAM.LIST' | translate }}</div>
<p-listbox
id="apm_iam_roles_list"
[options]="iamRoles"
[options]="roles"
[(ngModel)]="selectedIamRoles"
optionLabel="name"
[filter]="true"
Expand All @@ -148,9 +162,22 @@
/>
<div class="text-xs">{{ 'ROLE.IAM.INFO' | translate }}</div>
</div>

<ng-template pTemplate="footer">
<div class="flex flex-wrap column-gap-2 row-gap-1 justify-content-end">
<p-button
*ngIf="loadingExceptionKey || loading"
id="apm_iam_roles_action_close"
icon="pi pi-times"
(onClick)="onClose()"
[label]="'ACTIONS.NAVIGATION.CLOSE' | translate"
[ariaLabel]="'ACTIONS.NAVIGATION.CLOSE' | translate"
[pTooltip]="'ACTIONS.NAVIGATION.CLOSE.TOOLTIP' | translate"
tooltipPosition="top"
tooltipEvent="hover"
></p-button>
<p-button
*ngIf="!loadingExceptionKey && !loading"
id="apm_iam_roles_action_cancel"
icon="pi pi-times"
(onClick)="onClose()"
Expand All @@ -161,6 +188,7 @@
tooltipEvent="hover"
></p-button>
<p-button
*ngIf="!loadingExceptionKey && !loading"
id="apm_iam_roles_action_save"
icon="pi pi-check"
(onClick)="onAddIamRoles()"
Expand Down
16 changes: 8 additions & 8 deletions src/app/permission/role-detail/role-detail.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ describe('RoleDetailComponent', () => {
component.changeMode = 'EDIT'
component.formGroupRole = formGroup
component.showIamRolesDialog = true
spyOn(component, 'getIamRoles')
spyOn(component, 'searchIamRoles')

component.ngOnChanges()

expect(component.formGroupRole.enabled).toBeTrue()
expect(component.formGroupRole.controls['name'].value).toEqual(role.name)
expect(component.formGroupRole.controls['description'].value).toBeUndefined()
expect(component.getIamRoles).toHaveBeenCalled()
expect(component.searchIamRoles).toHaveBeenCalled()
})

it('should notify parent that nothing has changed after closing the dialog', () => {
Expand Down Expand Up @@ -208,26 +208,26 @@ describe('RoleDetailComponent', () => {
/**
* Select IAM Roles to be added
*/
describe('getIamRoles', () => {
xdescribe('searchIamRoles', () => {
it('should populate iamRoles with unique roles', () => {
const mockRoles = [{ name: 'Role1' }, { name: 'Role2' }]
const mockData = { stream: mockRoles }
roleApiSpy.searchAvailableRoles.and.returnValue(of(mockData))
component.roles = [{ name: 'Role2' }]

component.getIamRoles()
component.searchIamRoles()

expect(component.iamRoles.length).toBe(1)
expect(component.iamRoles).toContain(mockRoles[0])
expect(component.iamRoles).not.toContain(mockRoles[1])
//expect(component.iamRoles.length).toBe(1)
//expect(component.iamRoles).toContain(mockRoles[0])
//expect(component.iamRoles).not.toContain(mockRoles[1])
})

it('should handle error response', () => {
const errorResponse = { error: 'Error' }
spyOn(console, 'error')
roleApiSpy.searchAvailableRoles.and.returnValue(throwError(() => errorResponse))

component.getIamRoles()
component.searchIamRoles()

expect(console.error).toHaveBeenCalledWith('Error')
})
Expand Down
Loading

0 comments on commit c0a9fd4

Please sign in to comment.