This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git


The following commit(s) were added to refs/heads/master by this push:
     new ebd46ab  Adding role group mapping
ebd46ab is described below

commit ebd46ab1cec4d76e9b40b49a41ac7886417b029f
Author: Martin Stockhammer <[email protected]>
AuthorDate: Sun Jan 17 16:37:57 2021 +0100

    Adding role group mapping
---
 .../src/app/model/group-mapping.spec.ts            | 25 ++++++
 .../archiva-web/src/app/model/group-mapping.ts     | 23 ++++++
 .../main/archiva-web/src/app/model/group.spec.ts   | 25 ++++++
 .../src/main/archiva-web/src/app/model/group.ts    | 24 ++++++
 .../manage-roles-edit.component.html               | 71 ++++++++++++-----
 .../manage-roles-edit.component.ts                 | 93 ++++++++++++++++++++--
 .../manage-roles-list.component.html               |  1 +
 .../paginated-entities.component.ts                |  6 +-
 .../src/app/services/group.service.spec.ts         | 34 ++++++++
 .../archiva-web/src/app/services/group.service.ts  | 71 +++++++++++++++++
 .../src/main/archiva-web/src/assets/i18n/en.json   | 11 ++-
 11 files changed, 352 insertions(+), 32 deletions(-)

diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.spec.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.spec.ts
new file mode 100644
index 0000000..1f0b81c
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.spec.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { GroupMapping } from './group-mapping';
+
+describe('GroupMapping', () => {
+  it('should create an instance', () => {
+    expect(new GroupMapping()).toBeTruthy();
+  });
+});
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.ts
new file mode 100644
index 0000000..4643fe5
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group-mapping.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export class GroupMapping {
+    group_name:string;
+    unique_group_name:string;
+    roles:string[];
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.spec.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.spec.ts
new file mode 100644
index 0000000..4d70203
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.spec.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Group } from './group';
+
+describe('Group', () => {
+  it('should create an instance', () => {
+    expect(new Group()).toBeTruthy();
+  });
+});
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.ts
new file mode 100644
index 0000000..d5f6fc8
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/group.ts
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export class Group {
+    name:string;
+    unique_name:string;
+    description:string;
+    member_list:string[];
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
index bdbf7da..47ac7c0 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.html
@@ -86,9 +86,9 @@
         <ng-template ngbPanelHeader let-opened="opened">
             <div class="d-flex align-items-center justify-content-between">
                 <button ngbPanelToggle class="btn btn-link text-left 
shadow-none">
-                    <h3>{{'roles.edit.parents'|translate}}</h3></button>
-                <ng-container *ngIf="!opened"><i class="fa 
fa-eye-slash"></i></ng-container>
-                <ng-container *ngIf="opened"><i class="fa 
fa-eye"></i></ng-container>
+                    <h4>{{'roles.edit.parents'|translate}}</h4></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="opened"><i class="fa fa-eye-slash"></i></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="!opened"><i class="fa fa-eye"></i></button>
             </div>
         </ng-template>
         <ng-template ngbPanelContent>
@@ -103,9 +103,9 @@
         <ng-template ngbPanelHeader let-opened="opened">
             <div class="d-flex align-items-center justify-content-between">
                 <button ngbPanelToggle class="btn btn-link text-left 
shadow-none">
-                    <h3>{{'roles.edit.children'|translate}}</h3></button>
-                <ng-container *ngIf="!opened"><i class="fa 
fa-eye-slash"></i></ng-container>
-                <ng-container *ngIf="opened"><i class="fa 
fa-eye"></i></ng-container>
+                    <h4>{{'roles.edit.children'|translate}}</h4></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="opened"><i class="fa fa-eye-slash"></i></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="!opened"><i class="fa fa-eye"></i></button>
             </div>
         </ng-template>
         <ng-template ngbPanelContent>
@@ -122,9 +122,9 @@
             <div class="d-flex align-items-center justify-content-between">
 
                 <button ngbPanelToggle class="btn btn-link text-left 
shadow-none">
-                    <h3>{{'roles.edit.permissions'|translate}}</h3></button>
-                <ng-container *ngIf="!opened"><i class="fa 
fa-eye-slash"></i></ng-container>
-                <ng-container *ngIf="opened"><i class="fa 
fa-eye"></i></ng-container>
+                    <h4>{{'roles.edit.permissions'|translate}}</h4></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="opened"><i class="fa fa-eye-slash"></i></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="!opened"><i class="fa fa-eye"></i></button>
             </div>
         </ng-template>
         <ng-template ngbPanelContent>
@@ -154,9 +154,9 @@
         <ng-template ngbPanelHeader let-opened="opened">
             <div class="d-flex align-items-center justify-content-between">
                 <button ngbPanelToggle class="btn btn-link text-left 
shadow-none">
-                    <h3>{{'roles.edit.users'|translate}}</h3></button>
-                <ng-container *ngIf="!opened"><i class="fa 
fa-eye-slash"></i></ng-container>
-                <ng-container *ngIf="opened"><i class="fa 
fa-eye"></i></ng-container>
+                    <h4>{{'roles.edit.users'|translate}}</h4></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="opened"><i class="fa fa-eye-slash"></i></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="!opened"><i class="fa fa-eye"></i></button>
             </div>
         </ng-template>
         <ng-template ngbPanelContent>
@@ -244,8 +244,8 @@
                     <ngb-highlight [result]="r.user_id + ' - ' + r.full_name" 
[term]="t"></ngb-highlight>
                 </ng-template>
                 <div class="form-group">
-                    <label 
for="typeahead-http">{{'roles.edit.assignUserSearch'|translate}}</label>
-                    <input id="typeahead-http" type="text" class="form-control 
col-md-2"
+                    <label 
for="userSearchField">{{'roles.edit.assignUserSearch'|translate}}</label>
+                    <input id="userSearchField" type="text" 
class="form-control col-md-2"
                            name="userSearchField"
                            [class.is-invalid]="userSearchFailed" 
[resultTemplate]="userResultTemplate"
                            [inputFormatter]="getUserId"
@@ -260,12 +260,43 @@
             </form>
 
         </ng-template>
-        <div *ngIf="success">
-            Success
-        </div>
-        <div *ngIf="error">
-            <div>Error {{errorResult.error_messages}}</div>
-        </div>
     </ngb-panel>
 
+    <ngb-panel id="groupMapping">
+        <ng-template ngbPanelHeader let-opened="opened">
+            <div class="d-flex align-items-center justify-content-between">
+                <button ngbPanelToggle class="btn btn-link text-left 
shadow-none">
+                    <h4>{{'roles.edit.groupMapping'|translate}}</h4></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="opened"><i class="fa fa-eye-slash"></i></button>
+                <button class="btn btn-link shadow-none" ngbPanelToggle 
*ngIf="!opened"><i class="fa fa-eye"></i></button>
+            </div>
+        </ng-template>
+        <ng-template ngbPanelContent>
+            <ul class="list-group" >
+                <li class="list-group-item" *ngFor="let group of roleMappings$ 
|async">{{group}}</li>
+            </ul>
+            <form class="mt-2">
+                <ng-template #groupResultTemplate let-r="result" let-t="term">
+                    <ngb-highlight [result]="r.name" 
[term]="t"></ngb-highlight>
+                </ng-template>
+
+                <div class="form-group">
+                    <label 
for="groupSearchField">{{'roles.edit.assignGroupSearch'|translate}}</label>
+                    <input id="groupSearchField" type="text" 
class="form-control col-md-2"
+                           name="groupSearchField"
+                           [class.is-invalid]="groupSearchFailed" 
[resultTemplate]="groupResultTemplate"
+                           [inputFormatter]="getGroupName"
+                           [placement]="'top'"
+                           [(ngModel)]="groupSearchModel" 
[ngbTypeahead]="searchGroup" placeholder="Group Search"/>
+                    <small *ngIf="groupSearching"
+                           class="form-text text-muted">{{'form.searching'  
|translate}}</small>
+                    <div class="invalid-feedback"
+                         
*ngIf="groupSearchFailed">{{'roles.edit.groupSearchFailed'|translate}}</div>
+                </div>
+                <button class="btn btn-primary" 
(click)="assignGroupRole()">{{'roles.edit.assignButton'|translate}}</button>
+            </form>
+        </ng-template>
+    </ngb-panel>
+
+
 </ngb-accordion>
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
index 49ab9f3..59f8fea 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-edit/manage-roles-edit.component.ts
@@ -20,11 +20,11 @@ import {AfterContentInit, Component, EventEmitter, OnInit, 
Output, ViewChild} fr
 import {ActivatedRoute} from "@angular/router";
 import {FormBuilder, Validators} from "@angular/forms";
 import {RoleService} from "@app/services/role.service";
-import {catchError, debounceTime, distinctUntilChanged, filter, map, 
switchMap, tap} from "rxjs/operators";
+import {catchError, concatAll, debounceTime, distinctUntilChanged, filter, 
map, switchMap, tap} from "rxjs/operators";
 import {Role} from '@app/model/role';
 import {ErrorResult} from "@app/model/error-result";
 import {EditBaseComponent} from "@app/modules/shared/edit-base.component";
-import {forkJoin, Observable, of, zip} from 'rxjs';
+import {EMPTY, forkJoin, Observable, of, zip} from 'rxjs';
 import {RoleUpdate} from "@app/model/role-update";
 import {EntityService} from "@app/model/entity-service";
 import {User} from '@app/model/user';
@@ -34,6 +34,9 @@ import {UserInfo} from '@app/model/user-info';
 import {HttpResponse} from "@angular/common/http";
 import {PaginatedEntitiesComponent} from 
"@app/modules/shared/paginated-entities/paginated-entities.component";
 import {ToastService} from "@app/services/toast.service";
+import {GroupService} from "@app/services/group.service";
+import {GroupMapping} from "@app/model/group-mapping";
+import { Group } from '@app/model/group';
 
 @Component({
     selector: 'app-manage-roles-edit',
@@ -55,16 +58,22 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
 
     userSearching:boolean=false;
     userSearchFailed:boolean=false;
-
     public userSearchModel:any;
 
+    groupSearching:boolean=false;
+    groupSearchFailed:boolean=false;
+    public groupSearchModel:any;
+
     @ViewChild('userSection') roleUserComponent: 
PaginatedEntitiesComponent<UserInfo>;
     @ViewChild('userParentSection') roleUserParentComponent: 
PaginatedEntitiesComponent<UserInfo>;
 
     @Output()
     roleIdEvent: EventEmitter<string> = new EventEmitter<string>(true);
 
+    private roleMappings$;
+
     constructor(private route: ActivatedRoute, public roleService: 
RoleService, private userService: UserService,
+                private groupService: GroupService,
                 public fb: FormBuilder, private toastService: ToastService) {
         super(fb);
         super.init(fb.group({
@@ -119,6 +128,7 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
             if (this.roleUserParentComponent) {
                 
this.roleUserParentComponent.changeService(this.roleUserParentService);
             }
+            this.roleMappings$ = this.getMappedGroups(role.id);
         }, error => {
             this.editRole = new Role();
         });
@@ -232,10 +242,33 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
             tap(() => this.userSearching = false)
         )
 
+
     getUserId(item:UserInfo) : string {
         return item.user_id;
     }
 
+    searchGroup = (text$: Observable<string>) =>
+        text$.pipe(
+            debounceTime(300),
+            distinctUntilChanged(),
+            tap(() => this.groupSearching = true),
+            switchMap(term =>
+                this.groupService.query( term, 0, 10).pipe(
+                    tap(() => this.groupSearchFailed = false),
+                    map(pagedResult=>
+                        pagedResult.data),
+                    catchError(() => {
+                        this.groupSearchFailed = true;
+                        return of([]);
+                    }))
+            ),
+            tap(() => this.groupSearching = false)
+        )
+
+    getGroupName(item:Group) : string {
+        return item.name;
+    }
+
     showError(err: ErrorResult, errorKey:string, params:any={}) : void {
         let message = err.error_messages.length>0?err.error_messages[0]:''
         params['message']=message
@@ -243,7 +276,6 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
     }
 
     showSuccess(successKey:string, params:any={}) : void  {
-        console.log("Success " + successKey + " - " + JSON.stringify(params));
         
this.toastService.showSuccessByKey('manage-roles-edit',successKey,params)
     }
 
@@ -262,7 +294,7 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
                     this.error = true;
                     this.success = false;
                     this.errorResult = err;
-                    this.showError(err, 'roles.edit.errors.assignFailed', 
{'role_id':this.editRole.id,'user_id':userId})
+                    this.showError(err, 'roles.edit.errors.userAssignFailed', 
{'role_id':this.editRole.id,'user_id':userId})
                     return [];
                 })
             ).subscribe((response : HttpResponse<Role>)  => {
@@ -301,5 +333,56 @@ export class ManageRolesEditComponent extends 
EditBaseComponent<Role> implements
         }
     }
 
+    assignGroupRole() {
+        let groupName;
+        if (typeof(this.groupSearchModel)=='string') {
+            groupName=this.groupSearchModel;
+        } else {
+            if (this.groupSearchModel.name) {
+                groupName = this.groupSearchModel.name;
+            }
+        }
+        if (this.editRole.id!=null && groupName!=null && groupName.length>0) {
+            this.groupService.assignGroup(groupName, this.editRole.id).pipe(
+                catchError((err: ErrorResult) => {
+                    this.error = true;
+                    this.success = false;
+                    this.errorResult = err;
+                    this.showError(err, 'roles.edit.errors.groupAssignFailed', 
{'role_id':this.editRole.id,'group_name':groupName})
+                    return [];
+                })
+            ).subscribe((response : HttpResponse<any>)  => {
+                this.error = false;
+                this.success = true;
+                this.errorResult = null;
+                this.result = response.body;
+                
this.showSuccess('roles.edit.success.assignGroup',{'role_id':this.editRole.id,'group_name':groupName})
+                this.groupSearchModel=''
+            });
+        }
+    }
+
+
+    getMappedGroups(roleId:string) : Observable<string[]> {
+        console.log("Get mapped groups "+roleId);
+        if (roleId!=null && roleId.length>0) {
+            return this.groupService.getGroupMappings().pipe(
+                map((gMapArray: GroupMapping[]) => {
+                    console.log("Array " + gMapArray + " - " + 
gMapArray.length);
+                    let result = [];
+                    for (let gMap of gMapArray) {
+                        if (gMap.roles.includes(roleId)) {
+                            result.push(gMap.group_name);
+                        }
+                    }
+                    return result;
+                })
+            );
+        } else {
+            console.log("No role id found");
+            return EMPTY;
+        }
+    }
+
 }
 
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
index 01654af..94cbbd7 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/roles/manage-roles-list/manage-roles-list.component.html
@@ -17,6 +17,7 @@
   -->
 
 <app-paginated-entities [service]="service" pageSize="10" 
[(sortField)]="sortField" [(sortOrder)]="sortOrder"
+                        [id]="'rolesList'"
                         #parent>
     <ng-container *ngIf="parent.items$ |async as roleItemLoader" >
         <ng-template [ngIf]="roleItemLoader.loading" #spinner let-modal>
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
index 8c16052..ff54a5a 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/paginated-entities/paginated-entities.component.ts
@@ -24,18 +24,16 @@ import {
     filter,
     map,
     multicast,
-    pluck,
     refCount,
-    share,
     startWith,
-    switchMap, tap
+    switchMap,
+    tap
 } from "rxjs/operators";
 import {EntityService} from "@app/model/entity-service";
 import {FieldToggle} from "@app/model/field-toggle";
 import {PageQuery} from "../model/page-query";
 import {LoadingValue} from '../model/loading-value';
 import {PagedResult} from "@app/model/paged-result";
-import {Multipage} from "@app/model/multipage";
 import {PaginationInfo} from "@app/model/pagination-info";
 
 
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.spec.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.spec.ts
new file mode 100644
index 0000000..d860802
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.spec.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { GroupService } from './group.service';
+
+describe('GroupService', () => {
+  let service: GroupService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(GroupService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.ts
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.ts
new file mode 100644
index 0000000..d0f91a7
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/group.service.ts
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {Injectable} from '@angular/core';
+import {ArchivaRequestService} from "@app/services/archiva-request.service";
+import {GroupMapping} from "@app/model/group-mapping";
+import {catchError} from "rxjs/operators";
+import {HttpErrorResponse, HttpResponse} from "@angular/common/http";
+import {Observable, throwError} from 'rxjs';
+import {PagedResult} from "@app/model/paged-result";
+import {Group} from '@app/model/group';
+
+@Injectable({
+    providedIn: 'root'
+})
+export class GroupService {
+
+    constructor(private rest: ArchivaRequestService) {
+
+    }
+
+    getGroupMappings(): Observable<GroupMapping[]> {
+        return this.rest.executeRestCall<GroupMapping[]>("get", "redback", 
"groups/mappings", null).pipe(
+            catchError((error: HttpErrorResponse) => {
+                return throwError(this.rest.getTranslatedErrorResult(error));
+            })
+        )
+    }
+
+    assignGroup(groupName: string, roleId: string): 
Observable<HttpResponse<any>> {
+        return this.rest.executeResponseCall<any>("put", "redback", 
"groups/mappings/" + encodeURI(groupName) + "/roles/" + encodeURI(roleId), null)
+            .pipe(catchError((error: HttpErrorResponse) => {
+                return throwError(this.rest.getTranslatedErrorResult(error));
+            }));
+    }
+
+    public query(searchTerm: string, offset: number = 0, limit: number = 10, 
orderBy: string[] = ['name'], order: string = 'asc'): 
Observable<PagedResult<Group>> {
+        if (searchTerm == null) {
+            searchTerm = ""
+        }
+        if (orderBy == null || orderBy.length == 0) {
+            orderBy = ['id'];
+        }
+        return this.rest.executeRestCall<PagedResult<Group>>("get", "redback", 
"groups", {
+            'q': searchTerm,
+            'offset': offset,
+            'limit': limit,
+            'orderBy': orderBy,
+            'order': order
+        }).pipe(
+            catchError((error: HttpErrorResponse) => {
+                return throwError(this.rest.getTranslatedErrorResult(error));
+            }));
+    }
+
+}
diff --git 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
index 744f015..cf87a2a 100644
--- 
a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
+++ 
b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json
@@ -153,21 +153,26 @@
       "children": "Children",
       "permissions": "Permissions",
       "users": "Users",
+      "groupMapping": "Groups",
       "usersInstance": "Users Assigned to this Role",
       "usersParents": "Users Assigned to Parent Roles",
       "noUsersAssigned": "There are no users assigned to this role",
       "assignUserSearch": "Search and assign users to this role",
+      "assignGroupSearch": "Search and assign a group to this role",
       "userSearchFailed": "Sorry, could not load users",
+      "groupSearchFailed": "Sorry, could not get any groups",
       "assignButton": "Assign",
       "errors": {
         "updateFailed": "Could not update role {role_id}: {message}",
-        "assignFailed": "Could not assign role {role_id} to user {user_id}: 
{message}",
-        "retrieveFailed": "Could not retrieve role information: {message}"
+        "userAssignFailed": "Could not assign role {role_id} to user 
{user_id}: {message}",
+        "retrieveFailed": "Could not retrieve role information: {message}",
+        "groupAssignFailed": "Could not assign role {role_id} to group 
{group_name}: {message}"
       },
       "success": {
         "updated": "Role {role_id} was updated",
         "assign": "Role {role_id} was assigned to user {user_id}",
-        "unassign": "Removed assignment of {role_id} to {user_id}"
+        "unassign": "Removed assignment of {role_id} to {user_id}",
+        "assignGroup": "Role {role_id} was assigned to group {group_name}"
       }
     },
     "attributes": {

Reply via email to