Skip to content

Commit edadd20

Browse files
authored
Merge pull request #3412 from ProgrammeVitam/cc-15307-select-profiles
Story #15307: Clean code - create profile: deduplicate http requests & hide selects until options are available
2 parents c7db3e7 + 6b92fc0 commit edadd20

File tree

5 files changed

+104
-108
lines changed

5 files changed

+104
-108
lines changed

ui/ui-frontend/projects/archive-search/src/app/archive/archive-search/additional-actions-search/dip-request-create/dip-request-create.component.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
* The fact that you are presently reading this means that you have had
3535
* knowledge of the CeCILL-C license and that you accept its terms.
3636
*/
37-
import { Component, Inject, OnDestroy, OnInit, resource, ResourceRef } from '@angular/core';
37+
import { Component, Inject, OnDestroy, OnInit, ResourceRef } from '@angular/core';
3838
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
3939
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
4040
import { TranslateService } from '@ngx-translate/core';
41-
import { finalize, firstValueFrom, Subscription } from 'rxjs';
41+
import { finalize, Subscription } from 'rxjs';
4242
import * as uuid from 'uuid';
4343
import {
4444
AgencyService,
@@ -55,6 +55,7 @@ import {
5555
import { ArchiveService } from '../../../archive.service';
5656
import { ExportDIPRequestDto, QualifierVersion } from '../../../models/dip.interface';
5757
import { distinctUntilChanged, map } from 'rxjs/operators';
58+
import { rxResource } from '@angular/core/rxjs-interop';
5859

5960
@Component({
6061
selector: 'app-dip-request-create',
@@ -86,20 +87,18 @@ export class DipRequestCreateComponent implements OnInit, OnDestroy {
8687
},
8788
private snackBarService: SnackBarService,
8889
) {
89-
this.agencyOptionsResource = resource<VitamuiSelectOptions, void>({
90+
this.agencyOptionsResource = rxResource<VitamuiSelectOptions, void>({
9091
loader: () =>
91-
firstValueFrom(
92-
this.agencyService.getAll().pipe(
93-
map(
94-
(agencies) =>
95-
({
96-
options: agencies.map((agency) => ({
97-
key: agency.identifier,
98-
label: `${agency.identifier} - ${agency.name}`,
99-
})),
100-
customSorting: (a, b) => a.key.localeCompare(b.key),
101-
}) satisfies VitamuiSelectOptions,
102-
),
92+
this.agencyService.getAll().pipe(
93+
map(
94+
(agencies) =>
95+
({
96+
options: agencies.map((agency) => ({
97+
key: agency.identifier,
98+
label: `${agency.identifier} - ${agency.name}`,
99+
})),
100+
customSorting: (a, b) => a.key.localeCompare(b.key),
101+
}) satisfies VitamuiSelectOptions,
103102
),
104103
),
105104
});
Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,62 @@
1-
<div class="d-flex align-items-center">
2-
<div class="gap-2 align-items-stretch">
3-
<vitamui-select
4-
[options]="applications"
5-
[formControl]="appSelect"
6-
[placeholder]="'GROUP.PROFILE.MODAL.APP' | translate"
7-
></vitamui-select>
8-
<ng-container *ngIf="!tenantIdentifier">
1+
@if (loading) {
2+
<div class="vitamui-min-content">
3+
<mat-spinner class="vitamui-spinner medium"></mat-spinner>
4+
</div>
5+
} @else {
6+
<div class="d-flex align-items-center">
7+
<div class="gap-2 align-items-stretch">
98
<vitamui-select
10-
[options]="filteredTenants"
11-
[formControl]="tenantSelect"
12-
#tenantInput
13-
[placeholder]="'GROUP.PROFILE.MODAL.SAFE' | translate"
9+
[options]="applications"
10+
[formControl]="appSelect"
11+
[placeholder]="'GROUP.PROFILE.MODAL.APP' | translate"
1412
></vitamui-select>
15-
</ng-container>
16-
<vitamui-select
17-
[options]="filteredProfiles"
18-
[formControl]="profileSelect"
19-
[placeholder]="'GROUP.PROFILE.MODAL.PROFILE' | translate"
20-
#profileInput
21-
></vitamui-select>
22-
</div>
13+
<ng-container *ngIf="!tenantIdentifier">
14+
<vitamui-select
15+
[options]="filteredTenants"
16+
[formControl]="tenantSelect"
17+
#tenantInput
18+
[placeholder]="'GROUP.PROFILE.MODAL.SAFE' | translate"
19+
></vitamui-select>
20+
</ng-container>
21+
<vitamui-select
22+
[options]="filteredProfiles"
23+
[formControl]="profileSelect"
24+
[placeholder]="'GROUP.PROFILE.MODAL.PROFILE' | translate"
25+
#profileInput
26+
></vitamui-select>
27+
</div>
2328

24-
<div>
25-
<div class="d-flex flex-column align-items-center justify-content-center">
26-
<button type="button" class="btn primary" (click)="add()" [disabled]="!canAddProfile" #addButton>
27-
{{ 'COMMON.ADD' | translate }}
28-
</button>
29-
<button type="button" class="btn link" (click)="resetTree()">
30-
<i class="material-icons">replay</i>
31-
</button>
29+
<div>
30+
<div class="d-flex flex-column align-items-center justify-content-center">
31+
<button type="button" class="btn primary" (click)="add()" [disabled]="!canAddProfile" #addButton>
32+
{{ 'COMMON.ADD' | translate }}
33+
</button>
34+
<button type="button" class="btn link" (click)="resetTree()">
35+
<i class="material-icons">replay</i>
36+
</button>
37+
</div>
3238
</div>
3339
</div>
34-
</div>
3540

36-
<div class="vitamui-table mt-2 unscrollable">
37-
<div class="vitamui-table-head">
38-
<div class="col-3">{{ 'GROUP.PROFILE.MODAL.APP' | translate }}</div>
39-
<div *ngIf="!tenantIdentifier" class="col-3">{{ 'GROUP.PROFILE.MODAL.SAFE' | translate }}</div>
40-
<div class="col-5">{{ 'GROUP.PROFILE.MODAL.PROFILE' | translate }}</div>
41-
</div>
42-
<div *ngIf="!loading" class="vitamui-table-body">
43-
<div class="vitamui-table-rows" *ngFor="let id of profileIds; let index = index">
44-
<div class="vitamui-row d-flex align-items-center">
45-
<div class="col-3" vitamuiCommonEllipsis>{{ getApplicationFromId(id)?.name }}</div>
46-
<div *ngIf="!tenantIdentifier" class="col-3" vitamuiCommonEllipsis>{{ getProfileFromId(id)?.tenantName }}</div>
47-
<div class="col-5" vitamuiCommonEllipsis>{{ getProfileFromId(id)?.name }}</div>
48-
<div class="d-flex justify-content-end" [ngClass]="{ 'col-1': !tenantIdentifier, 'col-4': tenantIdentifier }">
49-
<button class="btn link" (click)="remove(index)">
50-
<i class="material-icons">clear</i>
51-
</button>
41+
<div class="vitamui-table mt-2 unscrollable">
42+
<div class="vitamui-table-head">
43+
<div class="col-3">{{ 'GROUP.PROFILE.MODAL.APP' | translate }}</div>
44+
<div *ngIf="!tenantIdentifier" class="col-3">{{ 'GROUP.PROFILE.MODAL.SAFE' | translate }}</div>
45+
<div class="col-5">{{ 'GROUP.PROFILE.MODAL.PROFILE' | translate }}</div>
46+
</div>
47+
<div *ngIf="!loading" class="vitamui-table-body">
48+
<div class="vitamui-table-rows" *ngFor="let id of profileIds; let index = index">
49+
<div class="vitamui-row d-flex align-items-center">
50+
<div class="col-3" vitamuiCommonEllipsis>{{ getApplicationFromId(id)?.name }}</div>
51+
<div *ngIf="!tenantIdentifier" class="col-3" vitamuiCommonEllipsis>{{ getProfileFromId(id)?.tenantName }}</div>
52+
<div class="col-5" vitamuiCommonEllipsis>{{ getProfileFromId(id)?.name }}</div>
53+
<div class="d-flex justify-content-end" [ngClass]="{ 'col-1': !tenantIdentifier, 'col-4': tenantIdentifier }">
54+
<button class="btn link" (click)="remove(index)">
55+
<i class="material-icons">clear</i>
56+
</button>
57+
</div>
5258
</div>
5359
</div>
5460
</div>
5561
</div>
56-
</div>
57-
<div *ngIf="loading" class="vitamui-min-content">
58-
<mat-spinner class="vitamui-spinner medium"></mat-spinner>
59-
</div>
62+
}

ui/ui-frontend/projects/identity/src/app/shared/profiles-form/profiles-form.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const expectedApp = {
141141
};
142142

143143
@Component({
144-
template: ` <app-profiles-form [(ngModel)]="profiles"></app-profiles-form> `,
144+
template: ` <app-profiles-form [(ngModel)]="profiles" level=""></app-profiles-form> `,
145145
standalone: false,
146146
})
147147
class TesthostComponent {

ui/ui-frontend/projects/identity/src/app/shared/profiles-form/profiles-form.component.ts

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@
3434
* The fact that you are presently reading this means that you have had
3535
* knowledge of the CeCILL-C license and that you accept its terms.
3636
*/
37-
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
37+
import { Component, ElementRef, forwardRef, Input, OnInit, SimpleChanges, ViewChild, OnChanges } from '@angular/core';
3838
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
39-
import { switchMap, tap } from 'rxjs/operators';
39+
import { map, shareReplay } from 'rxjs/operators';
4040
import { Application, ApplicationApiService, Option, Profile, ProfileService, SelectComponent } from 'vitamui-library';
4141

4242
import { HttpParams } from '@angular/common/http';
4343
import { OptionTree } from './option-tree.interface';
44+
import { zip } from 'rxjs';
4445

4546
export const PROFILES_FORM_VALUE_ACCESSOR: any = {
4647
provide: NG_VALUE_ACCESSOR,
@@ -55,29 +56,17 @@ export const PROFILES_FORM_VALUE_ACCESSOR: any = {
5556
providers: [PROFILES_FORM_VALUE_ACCESSOR],
5657
standalone: false,
5758
})
58-
export class ProfilesFormComponent implements ControlValueAccessor, OnInit {
59+
export class ProfilesFormComponent implements ControlValueAccessor, OnInit, OnChanges {
5960
profiles: Profile[] = [];
6061
profileIds: string[] = [];
6162
applicationsDetails: Application[] = [];
6263

6364
public loading = true;
6465

65-
@Input()
66-
showLevel = false;
67-
66+
@Input() showLevel = false;
6867
@Input() tenantIdentifier: number;
69-
7068
@Input() applicationNameExclude: string[];
71-
@Input()
72-
set level(level: string) {
73-
this._level = level;
74-
this.getProfiles();
75-
}
76-
77-
get level(): string {
78-
return this._level;
79-
}
80-
private _level: string;
69+
@Input() level: string;
8170

8271
appSelect = new FormControl(null, [Validators.required]);
8372
tenantSelect = new FormControl(null, [Validators.required]);
@@ -88,24 +77,30 @@ export class ProfilesFormComponent implements ControlValueAccessor, OnInit {
8877
filteredProfiles: Option[] = [];
8978

9079
@ViewChild('tenantInput', { static: false }) tenantInput: SelectComponent;
91-
@ViewChild('profileInput', { static: true }) profileInput: SelectComponent;
92-
@ViewChild('addButton', { static: true }) addButton: ElementRef;
80+
@ViewChild('profileInput', { static: false }) profileInput: SelectComponent;
81+
@ViewChild('addButton', { static: false }) addButton: ElementRef;
82+
83+
private applicationsDetails$ = this.appApiService.getAllByParams(new HttpParams().set('filterApp', 'false')).pipe(
84+
map((applications) => applications.APPLICATION_CONFIGURATION),
85+
shareReplay(1),
86+
);
9387

9488
constructor(
9589
private rngProfileService: ProfileService,
9690
private appApiService: ApplicationApiService,
9791
) {}
9892

9993
ngOnInit(): void {
100-
this.getProfiles();
94+
this.applicationsDetails$.subscribe((applicationsDetails) => (this.applicationsDetails = applicationsDetails));
95+
10196
this.appSelect.valueChanges.subscribe(() => {
10297
this.filterTenants();
10398
if (this.filteredTenants.length === 1) {
10499
this.tenantSelect.setValue(this.filteredTenants[0].key);
105100
} else {
106101
this.tenantSelect.setValue(null);
107102
if (!this.tenantIdentifier) {
108-
setTimeout(() => this.tenantInput.focus(), 0);
103+
setTimeout(() => this.tenantInput?.focus(), 0);
109104
}
110105
}
111106
this.toggleSelects();
@@ -121,26 +116,25 @@ export class ProfilesFormComponent implements ControlValueAccessor, OnInit {
121116
if (this.filteredProfiles.length === 1) {
122117
setTimeout(() => this.addButton.nativeElement.focus(), 0);
123118
} else {
124-
setTimeout(() => this.profileInput.focus(), 0);
119+
setTimeout(() => this.profileInput?.focus(), 0);
125120
}
126121
});
127122
}
128123

129-
getProfiles() {
130-
const params = new HttpParams().set('filterApp', 'false');
131-
this.appApiService
132-
.getAllByParams(params)
133-
.pipe(
134-
tap((applications) => (this.applicationsDetails = applications.APPLICATION_CONFIGURATION)),
135-
switchMap(() => this.rngProfileService.list(this.level, this.tenantIdentifier, this.applicationNameExclude)),
136-
)
137-
.subscribe((profiles) => {
138-
this.profiles = profiles;
139-
this.profileIds = this.filterProfileIds(this.profileIds, this.profiles, this.applicationsDetails);
140-
this.profileIds = this.profileIds.sort(byApplicationName(this.profiles, this.applicationsDetails));
141-
this.updateApplicationTree();
142-
this.loading = false;
143-
});
124+
ngOnChanges(_changes: SimpleChanges) {
125+
this.getProfiles();
126+
}
127+
128+
private getProfiles() {
129+
const rngProfiles$ = this.rngProfileService.list(this.level, this.tenantIdentifier, this.applicationNameExclude);
130+
131+
zip(this.applicationsDetails$, rngProfiles$).subscribe(([applicationsDetails, profiles]) => {
132+
this.profiles = profiles;
133+
this.profileIds = this.filterProfileIds(this.profileIds, this.profiles, applicationsDetails);
134+
this.profileIds = this.profileIds.sort(byApplicationName(this.profiles, applicationsDetails));
135+
this.updateApplicationTree();
136+
this.loading = false;
137+
});
144138
}
145139

146140
onChange = (_: any) => {};

ui/ui-frontend/projects/vitamui-library/src/lib/components/select/select.component.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {
4848
Injector,
4949
Input,
5050
QueryList,
51-
ResourceRef,
51+
Resource,
5252
Signal,
5353
ViewChild,
5454
ViewChildren,
@@ -173,13 +173,13 @@ export class SelectComponent extends AbstractFormInputDirective implements After
173173
}
174174

175175
private isResource(
176-
optionsParam: VitamuiSelectOptions | any[] | ResourceRef<VitamuiSelectOptions | any[]>,
177-
): optionsParam is ResourceRef<VitamuiSelectOptions | any[]> {
178-
return !!(optionsParam as ResourceRef<VitamuiSelectOptions | any[]>)?.isLoading;
176+
optionsParam: VitamuiSelectOptions | any[] | Resource<VitamuiSelectOptions | any[]>,
177+
): optionsParam is Resource<VitamuiSelectOptions | any[]> {
178+
return !!(optionsParam as Resource<VitamuiSelectOptions | any[]>)?.isLoading;
179179
}
180180

181181
@Input({ required: true })
182-
set options(optionsParam: VitamuiSelectOptions | any[] | ResourceRef<VitamuiSelectOptions | any[]>) {
182+
set options(optionsParam: VitamuiSelectOptions | any[] | Resource<VitamuiSelectOptions | any[]>) {
183183
if (this.isResource(optionsParam)) {
184184
this.optionsResource = optionsParam;
185185
} else {
@@ -213,7 +213,7 @@ export class SelectComponent extends AbstractFormInputDirective implements After
213213
this.addEventListeners();
214214
}
215215

216-
private optionsResource: ResourceRef<VitamuiSelectOptions | any[]>;
216+
private optionsResource: Resource<VitamuiSelectOptions | any[]>;
217217

218218
@Input() selectAllLabel = this.translateService.instant('SELECT.SELECT_ALL');
219219
@Input() allSelectedLabel?: string;

0 commit comments

Comments
 (0)