Fixed various issues for 2.2.0

This commit is contained in:
Timo Bejan 2021-08-04 10:41:41 +03:00
parent 5f84bb153e
commit 558acc023e
23 changed files with 155 additions and 159 deletions

View File

@ -102,8 +102,7 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "red-ui:build",
"proxyConfig": "proxy.config.json"
"browserTarget": "red-ui:build"
},
"configurations": {
"production": {

View File

@ -114,7 +114,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="bulkDelete([entity.dossierId])"
(action)="bulkDelete([entity])"
[tooltip]="'trash.action.delete' | translate"
icon="red:trash"
[type]="circleButtonTypes.dark"

View File

@ -11,6 +11,9 @@ import { SortingService } from '@services/sorting.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { DossiersService } from '../../../dossier/services/dossiers.service';
import { CircleButtonTypes } from '@iqser/common-ui';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { ConfirmationDialogInput, TitleColors } from '../../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
const HOURS_IN_A_DAY = 24;
const MINUTES_IN_AN_HOUR = 60;
@ -32,6 +35,7 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
protected readonly _injector: Injector,
private readonly _dossiersService: DossiersService,
private readonly _loadingService: LoadingService,
private readonly _adminDialogService: AdminDialogService,
private readonly _appConfigService: AppConfigService
) {
super(_injector);
@ -66,8 +70,8 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
return moment(softDeletedTime).add(this._deleteRetentionHours, 'hours').toISOString();
}
bulkDelete(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) {
this._loadingService.loadWhile(this._hardDelete(dossierIds));
bulkDelete(dossiers = this.screenStateService.selectedEntities) {
this._loadingService.loadWhile(this._hardDelete(dossiers));
}
bulkRestore(dossierIds = this.screenStateService.selectedEntities.map(d => d.dossierId)) {
@ -75,13 +79,31 @@ export class TrashScreenComponent extends BaseListingComponent<Dossier> implemen
}
private async _restore(dossierIds: string[]): Promise<void> {
this._loadingService.start();
await this._dossiersService.restore(dossierIds);
this._removeFromList(dossierIds);
this._loadingService.stop();
}
private async _hardDelete(dossierIds: string[]): Promise<void> {
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
private async _hardDelete(dossiers: Dossier[]): Promise<void> {
const period = this._appConfigService.getConfig('DELETE_RETENTION_HOURS');
const data = new ConfirmationDialogInput({
title: dossiers.length > 1 ? _('confirmation-dialog.delete-dossier.title-alt') : _('confirmation-dialog.delete-dossier.title'),
titleColor: TitleColors.PRIMARY,
question: _('confirmation-dialog.delete-dossier.question'),
// details: _('confirmation-dialog.delete-dossier.details'),
confirmationText: _('confirmation-dialog.delete-dossier.confirmation-text'),
requireInput: true,
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
translateParams: { dossierName: dossiers[0].dossierName, period: period }
});
this._adminDialogService.openDialog('confirm', null, data, async () => {
this._loadingService.start();
const dossierIds = dossiers.map(d => d.dossierId);
await this._dossiersService.hardDelete(dossierIds);
this._removeFromList(dossierIds);
this._loadingService.stop();
});
}
private _removeFromList(ids: string[]): void {

View File

@ -54,6 +54,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
async ngOnInit() {
await this._loadData();
this.searchService.setSearchKey('searchKey');
}
openAddEditUserDialog($event: MouseEvent, user?: User) {
@ -89,7 +90,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
}
private async _loadData() {
this.screenStateService.setEntities(await this._userControllerService.getAllUsers().toPromise());
this.screenStateService.setEntities(await this.userService.loadAllUsers());
await this.userService.loadAllUsers();
this._computeStats();
this._loadingService.stop();

View File

@ -15,7 +15,7 @@
<div class="all-caps-label" translate="dossier-details.owner"></div>
<div class="mt-12 d-flex">
<ng-container *ngIf="!editingOwner; else editOwner">
<redaction-initials-avatar [userId]="owner.userId" [withName]="true" color="gray" size="large"></redaction-initials-avatar>
<redaction-initials-avatar [userId]="owner?.userId" [withName]="true" color="gray" size="large"></redaction-initials-avatar>
<iqser-circle-button
(action)="editingOwner = true"

View File

@ -41,18 +41,20 @@ export class EditDossierDownloadPackageComponent implements OnInit, EditDossierS
}
get changed() {
for (const key of Object.keys(this.dossierForm.getRawValue())) {
if (this.dossierWrapper.dossier[key].length !== this.dossierForm.get(key).value.length) {
return true;
}
const originalItems = [...this.dossierWrapper.dossier[key]].sort();
const newItems = [...this.dossierForm.get(key).value].sort();
for (let idx = 0; idx < originalItems.length; ++idx) {
if (originalItems[idx] !== newItems[idx]) {
if (this.dossierForm) {
for (const key of Object.keys(this.dossierForm.getRawValue())) {
if (this.dossierWrapper.dossier[key].length !== this.dossierForm.get(key).value.length) {
return true;
}
const originalItems = [...this.dossierWrapper.dossier[key]].sort();
const newItems = [...this.dossierForm.get(key).value].sort();
for (let idx = 0; idx < originalItems.length; ++idx) {
if (originalItems[idx] !== newItems[idx]) {
return true;
}
}
}
}

View File

@ -57,7 +57,7 @@
<div class="dialog-actions">
<iqser-icon-button
(action)="openDeleteDossierDialog($event)"
(action)="deleteDossier($event)"
*ngIf="permissionsService.canDeleteDossier(dossierWrapper)"
[label]="'dossier-listing.delete.action' | translate"
icon="red:trash"

View File

@ -105,12 +105,11 @@ export class EditDossierGeneralInfoComponent implements OnInit, EditDossierSecti
if (updatedDossier) this.updateDossier.emit(updatedDossier);
}
openDeleteDossierDialog($event: MouseEvent) {
this._dialogService.openDeleteDossierDialog($event, this.dossierWrapper, () => {
this._editDossierDialogRef.componentInstance.afterSave();
this._editDossierDialogRef.close();
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
});
async deleteDossier($event: MouseEvent) {
await this._appStateService.deleteDossier(this.dossierWrapper);
this._editDossierDialogRef.componentInstance.afterSave();
this._editDossierDialogRef.close();
this._router.navigate(['main', 'dossiers']).then(() => this._notifyDossierDeleted());
}
private _notifyDossierDeleted() {

View File

@ -7,47 +7,53 @@
) | translate
}}
</div>
<form (submit)="confirm()" [formGroup]="redactionForm">
<div class="dialog-content">
{{
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.question'
: 'remove-annotations-dialog.remove-only-here.question'
) | translate
}}
<div class="dialog-content">
{{
(data.removeFromDictionary
? 'remove-annotations-dialog.remove-from-dictionary.question'
: 'remove-annotations-dialog.remove-only-here.question'
) | translate
}}
<div *ngIf="data.removeFromDictionary" class="content-wrapper">
<table class="default-table">
<thead>
<tr>
<th>{{ 'remove-annotations-dialog.dictionary' | translate }}</th>
<th>{{ 'remove-annotations-dialog.value' | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let annotation of data.annotationsToRemove">
<td>{{ annotation.dictionary }}</td>
<td>{{ annotation.value }}</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="data.removeFromDictionary" class="content-wrapper">
<table class="default-table">
<thead>
<tr>
<th>{{ 'remove-annotations-dialog.dictionary' | translate }}</th>
<th>{{ 'remove-annotations-dialog.value' | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let annotation of data.annotationsToRemove">
<td>{{ annotation.dictionary }}</td>
<td>{{ annotation.value }}</td>
</tr>
</tbody>
</table>
<ul *ngIf="!data.removeFromDictionary">
<li *ngFor="let annotation of data.annotationsToRemove">
{{ printable(annotation) }}
</li>
</ul>
<div [class.required]="!permissionsService.isApprover()" class="red-input-group w-300">
<label translate="manual-annotation.dialog.content.comment"></label>
<textarea formControlName="comment" name="comment" redactionHasScrollbar rows="4" type="text"></textarea>
</div>
</div>
<ul *ngIf="!data.removeFromDictionary">
<li *ngFor="let annotation of data.annotationsToRemove">
{{ printable(annotation) }}
</li>
</ul>
</div>
<div class="dialog-actions">
<button (click)="confirm()" color="primary" mat-flat-button>
{{ 'remove-annotations-dialog.confirm' | translate }}
</button>
<button (click)="deny()" color="primary" mat-flat-button>
{{ 'remove-annotations-dialog.deny' | translate }}
</button>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button type="submit" [disabled]="!redactionForm.valid">
{{ 'remove-annotations-dialog.confirm' | translate }}
</button>
<button (click)="deny()" color="primary" mat-flat-button>
{{ 'remove-annotations-dialog.deny' | translate }}
</button>
</div>
</form>
<iqser-circle-button class="dialog-close" icon="red:close" mat-dialog-close></iqser-circle-button>
</section>

View File

@ -3,6 +3,8 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { humanize } from '../../../../utils/functions';
import { PermissionsService } from '../../../../services/permissions.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
export interface RemoveAnnotationsDialogInput {
annotationsToRemove: AnnotationWrapper[];
@ -15,18 +17,26 @@ export interface RemoveAnnotationsDialogInput {
styleUrls: ['./remove-annotations-dialog.component.scss']
})
export class RemoveAnnotationsDialogComponent {
redactionForm: FormGroup;
constructor(
private readonly _translateService: TranslateService,
private readonly _formBuilder: FormBuilder,
public readonly permissionsService: PermissionsService,
public dialogRef: MatDialogRef<RemoveAnnotationsDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: RemoveAnnotationsDialogInput
) {}
) {
this.redactionForm = this._formBuilder.group({
comment: this.permissionsService.isApprover() ? [null] : [null, Validators.required]
});
}
deny() {
this.dialogRef.close();
}
confirm() {
this.dialogRef.close(true);
this.dialogRef.close(this.redactionForm.getRawValue().comment);
}
printable(annotation: AnnotationWrapper) {

View File

@ -105,7 +105,6 @@ export class DossierOverviewScreenComponent
async ngOnInit(): Promise<void> {
this._loadingService.start();
try {
this._fileDropOverlayService.initFileDropHandling();
@ -129,6 +128,7 @@ export class DossierOverviewScreenComponent
});
this.dossierAttributes = await this._dossierAttributesService.getValues(this.activeDossier);
this.searchService.setSearchKey('searchField');
} catch (e) {
} finally {
this._loadingService.stop();

View File

@ -120,7 +120,7 @@ export class SearchScreenComponent extends BaseListingComponent<ListItem> implem
dossierId: this._dossierId,
queryString: query ?? '',
from: 0,
returnSections: true,
returnSections: false,
size: 100
});
}

View File

@ -73,10 +73,10 @@ export class AnnotationActionsService {
removeFromDictionary: boolean,
annotationsChanged: EventEmitter<AnnotationWrapper>
) {
this._dialogService.openRemoveFromDictionaryDialog($event, annotations, removeFromDictionary, () => {
this._dialogService.openRemoveFromDictionaryDialog($event, annotations, removeFromDictionary, comment => {
annotations.forEach(annotation => {
this._processObsAndEmit(
this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation, removeFromDictionary),
this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation, removeFromDictionary, comment),
annotation,
annotationsChanged
);

View File

@ -190,33 +190,7 @@ export class DossiersDialogService extends DialogService<DialogType> {
});
ref.afterClosed().subscribe(async result => {
if (result) {
if (cb) cb();
}
});
return ref;
}
openDeleteDossierDialog($event: MouseEvent, dossier: DossierWrapper, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const period = this._appConfigService.getConfig('DELETE_RETENTION_HOURS');
const ref = this._dialog.open(ConfirmationDialogComponent, {
...dialogConfig,
data: new ConfirmationDialogInput({
title: _('confirmation-dialog.delete-dossier.title'),
titleColor: TitleColors.PRIMARY,
question: _('confirmation-dialog.delete-dossier.question'),
// details: _('confirmation-dialog.delete-dossier.details'),
confirmationText: _('confirmation-dialog.delete-dossier.confirmation-text'),
requireInput: true,
denyText: _('confirmation-dialog.delete-dossier.deny-text'),
translateParams: { dossierName: dossier.dossierName, period: period }
})
});
ref.afterClosed().subscribe(async result => {
if (result) {
await this._appStateService.deleteDossier(dossier);
if (cb) cb();
if (cb) cb(result);
}
});
return ref;
@ -287,25 +261,4 @@ export class DossiersDialogService extends DialogService<DialogType> {
return ref;
}
openRemoveAnnotationModal($event: MouseEvent, annotation: AnnotationWrapper, callback: () => void) {
$event?.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw'
});
ref.afterClosed().subscribe(result => {
if (result) {
this._manualAnnotationService.removeOrSuggestRemoveAnnotation(annotation).subscribe(() => {
if (callback) {
callback();
}
});
}
});
return ref;
}
}

View File

@ -161,7 +161,7 @@ export class ManualAnnotationService {
// this wraps
// /manualRedaction/redaction/remove/
// /manualRedaction/request/remove/
removeOrSuggestRemoveAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false) {
removeOrSuggestRemoveAnnotation(annotationWrapper: AnnotationWrapper, removeFromDictionary: boolean = false, comment: string) {
let mode: AnnotationActionMode,
body: any,
removeDict = false;
@ -176,7 +176,7 @@ export class ManualAnnotationService {
body = {
annotationId: annotationWrapper.id,
removeFromDictionary,
comment: '-'
comment: comment
};
removeDict = removeFromDictionary;
}
@ -185,7 +185,7 @@ export class ManualAnnotationService {
body = {
annotationId: annotationWrapper.id,
removeFromDictionary,
comment: '-'
comment: comment
};
removeDict = removeFromDictionary;
}

View File

@ -31,14 +31,16 @@
</div>
</div>
<div *ngFor="let filter of primaryFilters">
<ng-container
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
<div class="filter-content">
<div *ngFor="let filter of primaryFilters">
<ng-container
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
</div>
</div>
<div *ngIf="secondaryFilters?.length > 0" class="filter-options">

View File

@ -16,6 +16,11 @@
}
}
.filter-content {
max-height: 570px;
overflow: auto;
}
.filter-menu-options {
margin-top: 8px;
padding: 16px 16px 3px;

View File

@ -7,6 +7,7 @@ import { wipeCaches } from '@redaction/red-cache';
import { BASE_HREF } from '../tokens';
export interface ProfileModel {
username?: string;
email: string;
firstName: string;
lastName: string;
@ -16,7 +17,7 @@ export interface ProfileModel {
export class UserWrapper {
name: string;
constructor(private readonly _currentUser: KeycloakProfile, public roles: string[], public id: string) {
constructor(private readonly _currentUser: KeycloakProfile | User, public roles: string[], public id: string) {
this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
}
@ -40,6 +41,10 @@ export class UserWrapper {
return this._currentUser.lastName;
}
get searchKey() {
return this.name + this.username + this.email;
}
get isManager() {
return this.roles.indexOf('RED_MANAGER') >= 0;
}
@ -67,7 +72,7 @@ export class UserWrapper {
export class UserService {
usersReloaded$: EventEmitter<any> = new EventEmitter<any>();
private _currentUser: UserWrapper;
private _allUsers: User[];
private _allUsers: UserWrapper[];
constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
@ -75,9 +80,9 @@ export class UserService {
private readonly _userControllerService: UserControllerService
) {}
private _allRedUsers: User[];
private _allRedUsers: UserWrapper[];
get allRedUsers(): User[] {
get allRedUsers(): UserWrapper[] {
return this._allRedUsers;
}
@ -85,11 +90,11 @@ export class UserService {
return this._currentUser.id;
}
get managerUsers(): User[] {
get managerUsers(): UserWrapper[] {
return this._allRedUsers.filter(u => u.roles.indexOf('RED_MANAGER') >= 0);
}
get eligibleUsers(): User[] {
get eligibleUsers(): UserWrapper[] {
return this._allRedUsers.filter(u => u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0);
}
@ -97,7 +102,7 @@ export class UserService {
return this._currentUser;
}
private static _hasAnyRedRole(user: User) {
private static _hasAnyRedRole(user: UserWrapper) {
return (
user.roles.indexOf('RED_USER') >= 0 ||
user.roles.indexOf('RED_MANAGER') >= 0 ||
@ -111,20 +116,19 @@ export class UserService {
this._keycloakService.logout(window.location.origin + this._baseHref).then();
}
async loadRedUsersIfNecessary() {
async loadUsersIfNecessary() {
if (!this._allRedUsers) {
await this.loadRedUsers();
await this.loadAllUsers();
}
}
async loadRedUsers() {
const allRedUsers = await this._userControllerService.getUsers().toPromise();
this._allRedUsers = allRedUsers.filter(u => UserService._hasAnyRedRole(u));
}
async loadAllUsers() {
this._allUsers = await this._userControllerService.getAllUsers().toPromise();
this._allUsers = (await this._userControllerService.getAllUsers().toPromise()).map(
user => new UserWrapper(user, user.roles, user.userId)
);
this._allRedUsers = this._allUsers.filter(u => UserService._hasAnyRedRole(u));
this.usersReloaded$.next();
return this._allUsers;
}
async loadCurrentUser() {

View File

@ -15,11 +15,11 @@ export class AppStateGuard implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
if (this._userService.user.isUserAdmin) {
await this._userService.loadRedUsersIfNecessary();
await this._userService.loadUsersIfNecessary();
}
if (this._userService.user.isUser || this._userService.user.isAdmin) {
await this._userService.loadRedUsersIfNecessary();
await this._userService.loadUsersIfNecessary();
await this._appStateService.loadDossierTemplatesIfNecessary();
await this._appStateService.loadDictionaryDataIfNecessary();
}

View File

@ -1,6 +1,6 @@
{
"OAUTH_URL": "https://red-staging.iqser.cloud/auth/realms/redaction",
"API_URL": "https://red-staging.iqser.cloud/redaction-gateway-v1",
"OAUTH_URL": "https://dev-06.iqser.cloud/auth/realms/redaction",
"API_URL": "https://dev-06.iqser.cloud/redaction-gateway-v1",
"OAUTH_CLIENT_ID": "redaction",
"BACKEND_APP_VERSION": "4.4.40",
"FRONTEND_APP_VERSION": "1.1",

View File

@ -365,7 +365,8 @@
"deny-text": "Keep Dossier",
"input-label": "To proceed please type below",
"question": "Are you sure you want to delete this dossier?",
"title": "Delete {dossierName}"
"title": "Delete {dossierName}",
"title-alt": "Delete selected Dossiers"
},
"delete-file": {
"question": "Do you wish to proceed?",
@ -712,7 +713,7 @@
},
"download-includes": "Choose what is included at download:",
"download-status": {
"queued": "Your download has been queued, you can see all your requested downloads here: <a href='/main/downloads'>My Downloads<a/>."
"queued": "Your download has been queued, you can see all your requested downloads here: <a href='/ui/main/downloads'>My Downloads<a/>."
},
"download-type": {
"annotated": "Annotated PDF",

View File

@ -26,7 +26,7 @@
"lint": "nx workspace-lint && nx lint",
"lint-fix": "nx workspace-lint --fix && nx lint --fix",
"nx": "nx",
"start": "nx serve",
"start": "nx serve --base-href /ui/",
"test": "nx test",
"update": "nx migrate latest",
"workspace-generator": "nx workspace-generator"

View File

@ -1,8 +0,0 @@
{
"/auth/*": {
"target": "http://localhost:8080/",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}