Merge remote-tracking branch 'origin/master' into ui-updates

This commit is contained in:
Adina Țeudan 2020-10-22 01:54:09 +03:00
commit d0aa83f614
12 changed files with 189 additions and 87 deletions

View File

@ -3,7 +3,8 @@
## Swagger Generated Code
To regnerate http rune swaagger
```
URL=https://timo-redaction-dev.iqser.cloud/v2/api-docs?group=redaction-gateway-v1
BASE=http://ingress.redaction-timo-dev-3011.178.63.47.73.xip.io/
URL="$BASE"v2/api-docs?group=redaction-gateway-v1
mkdir -p /tmp/swagger
swagger-codegen generate -i "$URL" -l typescript-angular -o /tmp/swagger
```

View File

@ -3,17 +3,34 @@
class="dialog-header heading-l">
</div>
<div class="dialog-content">
<mat-list class="list-50vh">
<ng-container *ngFor="let user of userService.allUsers">
<mat-list-item class="pointer" [class.owner]="isOwner(user)"
(click)="assignOwner(user)"
*ngIf="userService.isManager(user)">
{{ userService.getNameForId(user.userId) }}
</mat-list-item>
</ng-container>
</mat-list>
</div>
<form (submit)="saveUsers()" [formGroup]="usersForm">
<div class="dialog-content">
<mat-form-field>
<mat-label>{{'assign-' + data.type + '-owner.dialog.single-user.label' | translate}}</mat-label>
<mat-select formControlName="singleUser">
<mat-option *ngFor="let user of userService.managerUsers" [value]="user.userId">
{{userService.getNameForId(user.userId)}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="data.type === 'project' ">
<mat-label>{{'assign-' + data.type + '-owner.dialog.multi-user.label' | translate}}</mat-label>
<mat-select formControlName="userList" multiple="true">
<mat-option *ngFor="let user of userService.allUsers" [value]="user.userId">
{{userService.getNameForId(user.userId)}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="dialog-actions">
<button color="primary" mat-flat-button type="submit"
[disabled]="!usersForm.valid"> {{'assign-' + data.type + '-owner.dialog.save.label' | translate}}</button>
</div>
</form>
<button (click)="dialogRef.close()" class="dialog-close" mat-icon-button>
<mat-icon svgIcon="red:close"></mat-icon>

View File

@ -1,14 +1,15 @@
import { Component, Inject } from '@angular/core';
import { Project, ProjectControllerService, User } from '@redaction/red-ui-http';
import { FileStatus, Project, ProjectControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { UserService } from '../../user/user.service';
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { NotificationService } from '../../notification/notification.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
class DialogData {
type: 'file' | 'project';
projectId?: string;
fileId?: string;
project?: Project;
file?: FileStatus;
}
@Component({
@ -17,43 +18,74 @@ class DialogData {
styleUrls: ['./assign-owner-dialog.component.scss']
})
export class AssignOwnerDialogComponent {
private project: Project;
public memberIds: string[];
constructor(private readonly _projectControllerService: ProjectControllerService,
usersForm: FormGroup;
constructor(public readonly userService: UserService,
private readonly _projectControllerService: ProjectControllerService,
private readonly _notificationService: NotificationService,
public readonly userService: UserService,
private readonly _formBuilder: FormBuilder,
private readonly _statusControllerService: StatusControllerService,
private readonly _appStateService: AppStateService,
public dialogRef: MatDialogRef<AssignOwnerDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData) {
this._loadData();
}
public isOwner(user: User) {
return this.data.type === 'project' ? this.project.ownerId === user.userId : false;
}
public assignOwner(user: User) {
if (this.data.type === 'project') {
this._projectControllerService.assignProjectOwner(this.project.projectId, user.userId).subscribe(() => {
this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(user.userId) + ' to project: ' + this.project.projectName);
}, error => {
this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR);
}).add(() => this._reloadProject());
} else if (this.data.type === 'file') {
console.log('not implemented yet');
}
}
private _loadData() {
if (this.data.type === 'project') {
this.project = this._appStateService.getProjectById(this.data.projectId).project;
const project = this.data.project;
this.usersForm = this._formBuilder.group({
singleUser: [project?.ownerId, Validators.required],
userList: [project?.memberIds]
});
}
if (this.data.type === 'file') {
const file = this.data.file;
this.usersForm = this._formBuilder.group({
singleUser: [file?.currentReviewer]
});
}
}
private _reloadProject() {
this._appStateService.addOrUpdateProject(this.project).then(() => {
this._loadData();
});
async saveUsers() {
if (this.data.type === 'project') {
const ownerId = this.usersForm.get('singleUser').value;
const memberIds = this.usersForm.get('userList').value;
const project = this.data.project;
await this._projectControllerService.addMembersToProject({ memberIds: memberIds }, project.projectId).toPromise();
await this._projectControllerService.assignProjectOwner(project.projectId, ownerId).toPromise();
const updatedProject = await this._projectControllerService.getProject(project.projectId).toPromise();
const toRemoveMembers = updatedProject.memberIds.filter(m => memberIds.indexOf(m) < 0);
if (toRemoveMembers.length > 0) {
await this._projectControllerService.deleteMembersToProject({ memberIds: toRemoveMembers }, project.projectId).toPromise();
}
project.ownerId = ownerId;
project.memberIds = [...new Set([ownerId, ...memberIds])];
this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(ownerId) + ' to project: ' + project.projectName);
//this._notificationService.showToastNotification('Failed: ' + error.error.message, null, NotificationType.ERROR);
}
if (this.data.type === 'file') {
const reviewerId = this.usersForm.get('singleUser').value;
await this._statusControllerService.assignProjectOwner1(this._appStateService.activeProjectId, this.data.file.fileId, reviewerId).toPromise();
this.data.file.currentReviewer = reviewerId;
this._notificationService.showToastNotification('Successfully assigned ' + this.userService.getNameForId(reviewerId) + ' to file: ' + this.data.file.filename);
}
this.dialogRef.close();
}
}

View File

@ -22,7 +22,7 @@ import { Annotations } from '@pdftron/webviewer';
const dialogConfig = {
width: '600px',
maxWidth: '90vw',
autoFocus: false,
autoFocus: false
};
@Injectable({
@ -49,8 +49,7 @@ export class DialogService {
public openDeleteFileDialog($event: MouseEvent, projectId: string, fileId: string, cb?: Function) {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
dialogRef.afterClosed().subscribe(result => {
this._dialog.open(ConfirmationDialogComponent, dialogConfig).afterClosed().subscribe(result => {
if (result) {
const file = this._appStateService.getFileById(projectId, fileId);
this._fileUploadControllerService.deleteFile(file.projectId, file.fileId).subscribe(async () => {
@ -76,12 +75,12 @@ export class DialogService {
public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
dialogRef.afterClosed().subscribe(result => {
this._dialog.open(ConfirmationDialogComponent, dialogConfig)
.afterClosed().subscribe(result => {
if (result) {
this._manualRedactionControllerService.approveRequest(projectId, fileId, annotationId).subscribe(() => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.success.label'), null, NotificationType.SUCCESS);
@ -94,15 +93,14 @@ export class DialogService {
public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw'
});
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
dialogRef.afterClosed().subscribe(result => {
this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw'
}).afterClosed().subscribe(result => {
if (result) {
this._manualRedactionControllerService.undo(projectId, fileId, annotationId).subscribe(ok => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS);
@ -125,8 +123,8 @@ export class DialogService {
public openDeleteProjectDialog($event: MouseEvent, project: Project, cb?: Function) {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
dialogRef.afterClosed().subscribe(async result => {
this._dialog.open(ConfirmationDialogComponent, dialogConfig)
.afterClosed().subscribe(async result => {
if (result) {
await this._appStateService.deleteProject(project);
if (cb) cb();
@ -142,6 +140,16 @@ export class DialogService {
});
}
public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus, cb?: Function) {
$event.stopPropagation();
this._dialog.open(AssignOwnerDialogComponent, {
...dialogConfig,
data: { type: 'file', file: file }
}).afterClosed().subscribe(() => {
if (cb) cb();
});
}
public openProjectDetailsDialog($event: MouseEvent, project: ProjectWrapper) {
$event.stopPropagation();
this._dialog.open(ProjectDetailsDialogComponent, {
@ -151,19 +159,20 @@ export class DialogService {
}
public openAddProjectDialog(cb?: Function): void {
const dialogRef = this._dialog.open(AddEditProjectDialogComponent, {
this._dialog.open(AddEditProjectDialogComponent, {
...dialogConfig,
autoFocus: true,
});
dialogRef.afterClosed().subscribe(result => {
autoFocus: true
}).afterClosed().subscribe(result => {
if (result && cb) cb();
});
}
public openAssignProjectMembersDialog(project: Project) {
public openAssignProjectMembersDialog(project: Project, cb?: Function) {
this._dialog.open(AssignProjectMembersDialogComponent, {
...dialogConfig,
data: project
}).afterClosed().subscribe(result => {
if (result && cb) cb();
});
}
}

View File

@ -96,7 +96,7 @@
[matTooltip]="'project-overview.report.action.label'|translate">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
<button (click)="assignPeopleToFile($event,fileStatus)" color="accent" mat-icon-button
<button (click)="openAssignFileOwnerDialog($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.assign.action.label'|translate">
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
@ -128,7 +128,7 @@
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
1
{{ appStateService.activeProject.project.memberIds.length }}
</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
@ -155,13 +155,12 @@
<div class="project-team mt-20">
<div class="all-caps-label" translate="project-overview.project-details.project-team.label"></div>
<div class="flex mt-20 members-container">
<div *ngFor="let username of members" class="member">
<div *ngFor="let username of displayMembers" class="member">
<redaction-initials-avatar [username]="username" size="large"></redaction-initials-avatar>
</div>
<!-- TODO THIS IS OVERFLOW-->
<!-- <div class="member">-->
<!-- <div class="oval large white-dark">+2</div>-->
<!-- </div>-->
<div class="member">
<div class="oval large white-dark">+{{overflowCount}}</div>
</div>
<div class="member pointer" (click)="openAssignProjectMembersDialog()">
<div class="oval red-white large">+</div>
</div>

View File

@ -75,6 +75,14 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return this.activeProject.memberIds.map(m => this._userService.getName(this._userService.getUserById(m)));
}
public get displayMembers() {
return this.members.slice(0, 6);
}
public get overflowCount() {
return this.members.length - 6;
}
public get ownerName() {
return this._userService.getNameForId(this.activeProject.ownerId);
}
@ -114,7 +122,15 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
}
public openAssignProjectMembersDialog() {
this._dialogService.openAssignProjectMembersDialog(this.activeProject);
this._dialogService.openAssignProjectMembersDialog(this.activeProject, () => {
this._getFileStatus();
});
}
public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus) {
this._dialogService.openAssignFileOwnerDialog($event, file, () => {
this._getFileStatus();
});
}
public reanalyseFile($event: MouseEvent, fileStatus: FileStatus) {
@ -149,14 +165,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return fileStatus === 'PROCESSING' || fileStatus === 'REVIEWED' || true;
}
public assignPeopleToFile($event: MouseEvent, fileStatus: FileStatus) {
$event.stopPropagation();
this._statusControllerService.assignProjectOwner1(this.appStateService.activeProjectId, fileStatus.fileId, this.user.id).subscribe(() => {
this._notificationService.showToastNotification('Successfully assigned ' + this.user.name + ' to file: ' + fileStatus.filename);
});
}
public getFileOwnerUsername(fileStatus: FileStatus) {
return undefined;
return this._userService.getNameForId(fileStatus.currentReviewer);
}
}

View File

@ -57,15 +57,15 @@ export class AppStateService {
get isActiveProjectOwner() {
return this._appState.activeProject.project.ownerId === this._userService.userId
return this._appState.activeProject?.project?.ownerId === this._userService.userId
}
get isActiveProjectMember() {
return this._appState.activeProject.project.memberIds.indexOf(this._userService.userId) >= 0;
return this._appState.activeProject?.project?.memberIds?.indexOf(this._userService.userId) >= 0;
}
get isActiveFileDocumentReviewer() {
return false;
return this._appState.activeFile?.currentReviewer === this._userService.userId;
}

View File

@ -56,6 +56,10 @@ export class UserService {
return this._allUsers;
}
get managerUsers() {
return this._allUsers.filter(u => u.roles.indexOf('RED_MANAGER') >= 0);
}
async loadAllUsersIfNecessary() {
if (!this._allUsers) {
await this.loadAllUsers();
@ -64,7 +68,7 @@ export class UserService {
async loadAllUsers() {
const allUsers = await this._userControllerService.getAllUsers({}, 0, 100).toPromise();
this._allUsers = allUsers.users;
this._allUsers = allUsers.users.filter(u => this._hasAnyRedRole(u));
return allUsers;
}
@ -100,4 +104,8 @@ export class UserService {
isUser(user: User) {
return user.roles.indexOf('RED_USER') >= 0;
}
private _hasAnyRedRole(u: User) {
return u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0 || u.roles.indexOf('RED_ADMIN') >= 0;
}
}

View File

@ -457,6 +457,38 @@
"label": "Unassigned"
}
},
"assign-file-owner":{
"dialog": {
"single-user": {
"label": "Reviewer"
},
"title":{
"label": "Manage File Reviewer"
},
"save": {
"label": "Save"
}
}
},
"assign-project-owner":{
"dialog": {
"single-user": {
"label": "Owner"
},
"multi-user": {
"label": "Members"
},
"title":{
"label": "Manage Project Owner and Members"
},
"save": {
"label": "Save"
}
}
},
"unassigned": "Unassigned",
"under-review": "Under review",
"under-approval": "Under approval",

View File

@ -257,15 +257,6 @@ export class ProjectControllerService {
headers = headers.set('Accept', httpHeaderAcceptSelected);
}
// to determine the Content-Type header
const consumes: string[] = [
'*/*'
];
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
if (httpContentTypeSelected !== undefined) {
headers = headers.set('Content-Type', httpContentTypeSelected);
}
return this.httpClient.request<any>('delete', `${this.basePath}/project/members/${encodeURIComponent(String(projectId))}`,
{
body: body,

View File

@ -18,6 +18,10 @@ export interface FileStatus {
* Date and time when the file was added to the system.
*/
added?: string;
/**
* The current reviewer's (if any) user id.
*/
currentReviewer?: string;
/**
* The ID of the file.
*/

View File

@ -1,6 +1,6 @@
{
"name": "redaction",
"version": "0.0.69",
"version": "0.0.70",
"license": "MIT",
"scripts": {
"build-lint-all": "ng lint --project=red-ui-http --fix && ng build --project=red-ui-http && ng lint --project=red-ui --fix && ng build --project=red-ui --prod",