Skip to content

Commit 6a68bda

Browse files
feat: admin can require user groups to be added in applications
Signed-off-by: aditya.goyal <aditya.goyal@graviteesource.com>
1 parent cdd3268 commit 6a68bda

File tree

33 files changed

+508
-174
lines changed

33 files changed

+508
-174
lines changed

gravitee-apim-console-webui/src/entities/application/CreateApplication.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export interface CreateApplication {
2222
domain?: string;
2323
type?: string;
2424
settings?: ApplicationSettings;
25+
groups?: string[];
2526
}

gravitee-apim-console-webui/src/entities/consoleSettings.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface ConsoleSettings {
3333
trialInstance?: ConsoleSettingsTrialInstance;
3434
federation?: ConsoleSettingsFederation;
3535
cloudHosted?: ConsoleSettingsCloudHosted;
36-
userGroups?: ConsoleSettingsUserGroups;
36+
userGroup?: ConsoleSettingsUserGroup;
3737
}
3838

3939
export interface ConsoleSettingsEmail {
@@ -189,6 +189,6 @@ export interface ConsoleSettingsCloudHosted {
189189
enabled?: boolean;
190190
}
191191

192-
interface ConsoleSettingsUserGroups {
193-
required?: boolean;
192+
interface ConsoleSettingsUserGroup {
193+
required: { enabled: boolean };
194194
}

gravitee-apim-console-webui/src/management/application/creation-ng/application-creation.component.spec.ts

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ describe('ApplicationCreationComponent', () => {
6868
name: 'name',
6969
description: 'description',
7070
domain: 'domain',
71+
groups: [],
7172
settings: {
7273
app: {
7374
client_id: 'appClientId',
@@ -97,6 +98,7 @@ describe('ApplicationCreationComponent', () => {
9798
name: 'name',
9899
description: 'description',
99100
domain: 'domain',
101+
groups: [],
100102
settings: {
101103
oauth: {
102104
additional_client_metadata: {},
@@ -128,6 +130,7 @@ describe('ApplicationCreationComponent', () => {
128130
name: 'name',
129131
description: 'description',
130132
domain: 'domain',
133+
groups: [],
131134
settings: {
132135
oauth: {
133136
additional_client_metadata: {},
@@ -166,6 +169,7 @@ describe('ApplicationCreationComponent', () => {
166169
name: 'name',
167170
description: 'description',
168171
domain: 'domain',
172+
groups: [],
169173
settings: {
170174
app: {
171175
client_id: 'appClientId',

gravitee-apim-console-webui/src/management/application/creation-ng/application-creation.component.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { CommonModule } from '@angular/common';
18-
import { Component, ChangeDetectionStrategy, DestroyRef, inject } from '@angular/core';
18+
import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit } from '@angular/core';
1919
import { MatCardModule } from '@angular/material/card';
2020
import { map, tap } from 'rxjs/operators';
2121
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@@ -30,6 +30,8 @@ import { ApplicationTypesService } from '../../../services-ngx/application-types
3030
import { ApplicationService } from '../../../services-ngx/application.service';
3131
import { SnackBarService } from '../../../services-ngx/snack-bar.service';
3232
import { toDictionary } from '../../../util/gio-form-header.util';
33+
import { ConsoleSettings } from '../../../entities/consoleSettings';
34+
import { ConsoleSettingsService } from '../../../services-ngx/console-settings.service';
3335

3436
const TYPES_INFOS = {
3537
SIMPLE: {
@@ -67,9 +69,10 @@ const TYPES_INFOS = {
6769
styleUrls: ['./application-creation.component.scss'],
6870
templateUrl: './application-creation.component.html',
6971
})
70-
export class ApplicationCreationComponent {
72+
export class ApplicationCreationComponent implements OnInit {
7173
private destroyRef = inject(DestroyRef);
7274
private isCreating = false;
75+
private consoleSettings: ConsoleSettings = {};
7376

7477
public applicationFormGroup = new FormGroup<ApplicationForm>({
7578
name: new FormControl(undefined, Validators.required),
@@ -84,6 +87,7 @@ export class ApplicationCreationComponent {
8487
oauthGrantTypes: new FormControl(),
8588
oauthRedirectUris: new FormControl([]),
8689
additionalClientMetadata: new FormControl([]),
90+
groups: new FormControl([]),
8791
});
8892

8993
public applicationTypes$ = this.applicationTypesService.getEnabledApplicationTypes().pipe(
@@ -111,8 +115,13 @@ export class ApplicationCreationComponent {
111115
private readonly snackBarService: SnackBarService,
112116
private readonly activatedRoute: ActivatedRoute,
113117
private readonly router: Router,
118+
private readonly settingsService: ConsoleSettingsService,
114119
) {}
115120

121+
ngOnInit(): void {
122+
this.setUserGroupRequiredValidator();
123+
}
124+
116125
onSubmit() {
117126
if (this.applicationFormGroup.invalid || this.isCreating === true) {
118127
return;
@@ -125,6 +134,7 @@ export class ApplicationCreationComponent {
125134
name: applicationPayload.name,
126135
description: applicationPayload.description,
127136
domain: applicationPayload.domain,
137+
groups: applicationPayload.groups,
128138
settings: {
129139
...(applicationPayload.type === 'SIMPLE'
130140
? {
@@ -158,4 +168,15 @@ export class ApplicationCreationComponent {
158168
},
159169
});
160170
}
171+
172+
private setUserGroupRequiredValidator() {
173+
this.settingsService.get().subscribe({
174+
next: (response) => {
175+
this.consoleSettings = response;
176+
if (this.consoleSettings.userGroup.required.enabled) {
177+
this.applicationFormGroup.get('groups').setValidators(Validators.required);
178+
}
179+
},
180+
});
181+
}
161182
}

gravitee-apim-console-webui/src/management/application/creation-ng/components/application-creation-form.component.html

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@
5050
<mat-label>Domain</mat-label>
5151
</mat-form-field>
5252

53+
<mat-form-field>
54+
<mat-label>Select Groups</mat-label>
55+
<mat-hint>Select user groups to add to the application</mat-hint>
56+
<mat-select formControlName="groups" multiple>
57+
@for (group of groupsList | async; track group) {
58+
<mat-option [value]="group.id">{{ group.name }}</mat-option>
59+
}
60+
</mat-select>
61+
</mat-form-field>
62+
5363
<h3 class="form__securityTitle">Security</h3>
5464
@if (applicationTypes.length === 0) {
5565
<gio-banner-warning>No application type available, please check Client Registration configuration.</gio-banner-warning>

gravitee-apim-console-webui/src/management/application/creation-ng/components/application-creation-form.component.ts

+19-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { CommonModule } from '@angular/common';
17-
import { Component, OnInit, inject, DestroyRef, Input } from '@angular/core';
17+
import { Component, Input, OnInit } from '@angular/core';
1818
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
1919
import { MatFormFieldModule } from '@angular/material/form-field';
2020
import { MatInputModule } from '@angular/material/input';
@@ -28,11 +28,13 @@ import {
2828
Header,
2929
} from '@gravitee/ui-particles-angular';
3030
import { MatRadioModule } from '@angular/material/radio';
31-
import { filter, map, share, startWith, tap } from 'rxjs/operators';
31+
import { filter, map, share, startWith, switchMap, tap } from 'rxjs/operators';
3232
import { MatSelectModule } from '@angular/material/select';
33-
import { Observable } from 'rxjs';
33+
import { BehaviorSubject, Observable } from 'rxjs';
3434

3535
import { ApplicationType } from '../../../../entities/application-type/ApplicationType';
36+
import { GroupService } from '../../../../services-ngx/group.service';
37+
import { Group } from '../../../../entities/group/group';
3638

3739
export type ApplicationForm = {
3840
name: FormControl<string>;
@@ -48,6 +50,7 @@ export type ApplicationForm = {
4850
oauthRedirectUris: FormControl<string[]>;
4951

5052
additionalClientMetadata: FormControl<Header[]>;
53+
groups: FormControl<string[]>;
5154
};
5255

5356
export type ApplicationCreationFormApplicationType = ApplicationType & {
@@ -77,8 +80,6 @@ export type ApplicationCreationFormApplicationType = ApplicationType & {
7780
templateUrl: './application-creation-form.component.html',
7881
})
7982
export class ApplicationCreationFormComponent implements OnInit {
80-
private destroyRef = inject(DestroyRef);
81-
8283
@Input({ required: true })
8384
public applicationTypes: ApplicationCreationFormApplicationType[];
8485

@@ -92,8 +93,14 @@ export class ApplicationCreationFormComponent implements OnInit {
9293
allowedGrantTypesVM: { value: string; label: string; disabled: boolean }[];
9394
}
9495
>;
96+
groupsList: Observable<Group[]>;
97+
refreshGroups = new BehaviorSubject<Group[]>([]);
98+
99+
constructor(private readonly groupService: GroupService) {}
95100

96101
ngOnInit() {
102+
this.initializeGroups();
103+
97104
this.applicationType$ = this.applicationFormGroup.get('type').valueChanges.pipe(
98105
startWith(this.applicationFormGroup.get('type').value),
99106
filter((typeSelected) => !!typeSelected),
@@ -133,4 +140,11 @@ export class ApplicationCreationFormComponent implements OnInit {
133140
share(),
134141
);
135142
}
143+
144+
private initializeGroups() {
145+
this.groupsList = this.refreshGroups.pipe(
146+
switchMap((_) => this.groupService.list()),
147+
map((_) => _.sort((a, b) => a.name.localeCompare(b.name))),
148+
);
149+
}
136150
}

gravitee-apim-console-webui/src/management/settings/groups/group/add-members-dialog/add-members-dialog.component.html

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<!--
22
33
Copyright (C) 2015 The Gravitee team (http://gravitee.io)
4-
4+
55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
77
You may obtain a copy of the License at
8-
8+
99
http://www.apache.org/licenses/LICENSE-2.0
10-
10+
1111
Unless required by applicable law or agreed to in writing, software
1212
distributed under the License is distributed on an "AS IS" BASIS,
1313
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -58,11 +58,12 @@ <h2 mat-dialog-title>Add members</h2>
5858
be transferred while editing the group members.
5959
</mat-error>
6060

61-
<mat-list role="list">
62-
<mat-list-item *ngFor="let user of selectedUsers">
63-
{{ user.displayName }} <button mat-button (click)="deselectUser(user)"><mat-icon svgIcon="gio:trash"></mat-icon></button>
64-
</mat-list-item>
65-
</mat-list>
61+
<mat-chip-set>
62+
<mat-chip *ngFor="let user of selectedUsers" [removable]="true" (removed)="deselectUser(user)">
63+
{{ user.displayName }}
64+
<mat-icon matChipRemove>cancel</mat-icon>
65+
</mat-chip>
66+
</mat-chip-set>
6667
</form>
6768

6869
<mat-dialog-actions style="justify-content: flex-end">

gravitee-apim-console-webui/src/management/settings/groups/group/add-members-dialog/add-members-dialog.component.spec.ts

+24-13
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,36 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { ComponentFixture, TestBed } from '@angular/core/testing';
16+
import { TestBed } from '@angular/core/testing';
17+
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
1718

1819
import { AddMembersDialogComponent } from './add-members-dialog.component';
1920

20-
describe('AddMembersDialogComponent', () => {
21-
let component: AddMembersDialogComponent;
22-
let fixture: ComponentFixture<AddMembersDialogComponent>;
23-
24-
beforeEach(async () => {
25-
await TestBed.configureTestingModule({
26-
imports: [AddMembersDialogComponent],
27-
}).compileComponents();
21+
import { GioTestingModule } from '../../../../../shared/testing';
2822

29-
fixture = TestBed.createComponent(AddMembersDialogComponent);
30-
component = fixture.componentInstance;
31-
fixture.detectChanges();
32-
});
23+
describe('AddMembersDialogComponent', () => {
24+
TestBed.configureTestingModule({
25+
imports: [AddMembersDialogComponent, MatDialogModule, GioTestingModule],
26+
declarations: [],
27+
providers: [
28+
{
29+
provide: MatDialogRef,
30+
useValue: {
31+
close: jest.fn(),
32+
},
33+
},
34+
{
35+
provide: MAT_DIALOG_DATA,
36+
useValue: {
37+
/* mock data */
38+
},
39+
},
40+
],
41+
}).compileComponents();
3342

3443
it('should create', () => {
44+
const fixture = TestBed.createComponent(AddMembersDialogComponent);
45+
const component = fixture.componentInstance;
3546
expect(component).toBeTruthy();
3647
});
3748
});

gravitee-apim-console-webui/src/management/settings/groups/group/add-members-dialog/add-members-dialog.component.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { UsersService } from 'src/services-ngx/users.service';
1716

1817
import { Component, Inject, OnInit } from '@angular/core';
1918
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
@@ -28,6 +27,7 @@ import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operato
2827
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
2928
import { Observable, of, switchMap } from 'rxjs';
3029
import { MatListModule } from '@angular/material/list';
30+
import { MatChipsModule } from '@angular/material/chips';
3131

3232
import { GioPermissionService } from '../../../../../shared/components/gio-permission/gio-permission.service';
3333
import { GroupService } from '../../../../../services-ngx/group.service';
@@ -40,7 +40,7 @@ import { SearchableUser } from '../../../../../entities/user/searchableUser';
4040
import { Member } from '../../../../../entities/management-api-v2';
4141
import { GroupMembership } from '../../../../../entities/group/groupMember';
4242
import { RoleName } from '../membershipState';
43-
43+
import { UsersService } from '../../../../../services-ngx/users.service';
4444

4545
@Component({
4646
selector: 'add-member-dialog',
@@ -56,6 +56,7 @@ import { RoleName } from '../membershipState';
5656
MatSelectModule,
5757
MatAutocompleteModule,
5858
MatListModule,
59+
MatChipsModule,
5960
],
6061
templateUrl: './add-members-dialog.component.html',
6162
styleUrl: './add-members-dialog.component.scss',
@@ -196,9 +197,8 @@ export class AddMembersDialogComponent implements OnInit {
196197

197198
return this.usersService.search(searchTerm).pipe(
198199
map((users) => {
199-
const excludedIds = this.members.map((member) => member.id);
200-
excludedIds.concat(this.selectedUsers.map((user) => user.id));
201-
return users.filter((user) => !excludedIds.includes(user.id));
200+
const excludedIds = new Set([...this.members.map((member) => member.id), ...this.selectedUsers.map((user) => user.id)]);
201+
return users.filter((user) => !excludedIds.has(user.id));
202202
}),
203203
);
204204
}

gravitee-apim-console-webui/src/management/settings/groups/group/delete-member-dialog/delete-member-dialog.component.html

+5-5
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ <h2 mat-dialog-title>Delete Member</h2>
3030
<mat-hint *ngIf="!selectedPrimaryOwner">{{ 'Please search members to transfer ownership' }}</mat-hint>
3131
</mat-form-field>
3232

33-
<mat-list role="list" *ngIf="selectedPrimaryOwner">
34-
<mat-list-item>
33+
<mat-chip-set *ngIf="selectedPrimaryOwner">
34+
<mat-chip [removable]="true" (removed)="deselectPrimaryOwner()">
3535
{{ selectedPrimaryOwner.displayName }}
36-
<button mat-button (click)="deselectPrimaryOwner()"><mat-icon svgIcon="gio:trash"></mat-icon></button>
37-
</mat-list-item>
38-
</mat-list>
36+
<mat-icon matChipRemove>cancel</mat-icon>
37+
</mat-chip>
38+
</mat-chip-set>
3939

4040
<mat-hint *ngIf="this.ownershipTransferMessage">{{ this.ownershipTransferMessage }}</mat-hint>
4141
<span> Are you sure, you want to delete {{ member.displayName }}? </span>

0 commit comments

Comments
 (0)