Merge branch 'VM/RED-9996' into 'master'

RED-9996 - pre-filter available annotation filter types in new DocuMine component view

Closes RED-9996

See merge request redactmanager/red-ui!706
This commit is contained in:
Nicoleta Panaghiu 2024-11-18 17:26:16 +01:00
commit edadf1ea41
10 changed files with 87 additions and 51 deletions

View File

@ -1,10 +1,10 @@
import { Component, input, Input } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { Dossier, File } from '@red/domain';
import { ComponentLogService } from '@services/files/component-log.service';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { ComponentLogService } from '@services/entity-services/component-log.service';
@Component({
selector: 'redaction-documine-export',

View File

@ -88,7 +88,7 @@ export class EditableStructuredComponentValueComponent implements OnInit {
}
this.deselectLast.emit();
this.selected = true;
this._state.componentReferenceIds = this.#getUniqueReferencesIds(this.currentEntry().componentValues);
this._state.componentReferenceIds.set(this.#getUniqueReferencesIds(this.currentEntry().componentValues));
}
}
@ -104,7 +104,7 @@ export class EditableStructuredComponentValueComponent implements OnInit {
$event?.stopImmediatePropagation();
this.selected = false;
this.editing = false;
this._state.componentReferenceIds = null;
this._state.componentReferenceIds.set(null);
}
cancel($event?: MouseEvent) {

View File

@ -3,7 +3,7 @@
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'" [attr.help-mode-key]="'filter_components'"></iqser-popup-filter>
</div>
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container" id="components-view">
<div *ngIf="componentLogService.all$ | async as components" class="components-container" id="components-view">
<div class="component-row">
<div class="header">
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>
@ -12,7 +12,7 @@
<div class="row-separator"></div>
</div>
<div *ngFor="let entry of displayedComponents" class="component-row">
<div *ngFor="let entry of components" class="component-row">
<redaction-editable-structured-component-value
#editableComponent
[entry]="entry"

View File

@ -2,16 +2,14 @@ import { Component, effect, Input, OnInit, signal, ViewChildren } from '@angular
import { List } from '@common-ui/utils';
import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { ComponentLogEntry, Dictionary, File, IComponentLogEntry, WorkflowFileStatuses } from '@red/domain';
import { ComponentLogService } from '@services/files/component-log.service';
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
import { EditableStructuredComponentValueComponent } from '../editable-structured-component-value/editable-structured-component-value.component';
import { FilterService, PopupFilterComponent } from '@common-ui/filtering';
import { ComponentLogFilterService } from '../../services/component-log-filter.service';
import { map } from 'rxjs/operators';
import { toObservable } from '@angular/core/rxjs-interop';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { FilePreviewStateService } from '../../services/file-preview-state.service';
import { ComponentLogService } from '@services/entity-services/component-log.service';
@Component({
selector: 'redaction-structured-component-management',
@ -21,20 +19,17 @@ import { FilePreviewStateService } from '../../services/file-preview-state.servi
imports: [PopupFilterComponent, NgIf, AsyncPipe, TranslateModule, NgForOf, EditableStructuredComponentValueComponent],
})
export class StructuredComponentManagementComponent implements OnInit {
protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
protected readonly componentLogData$ = toObservable(this.componentLogData);
protected readonly iconButtonTypes = IconButtonTypes;
protected displayedComponents$: Observable<ComponentLogEntry[]>;
@Input() file: File;
@Input() dictionaries: Dictionary[];
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
constructor(
private readonly _componentLogService: ComponentLogService,
private readonly _loadingService: LoadingService,
private readonly _componentLogFilterService: ComponentLogFilterService,
private readonly _filterService: FilterService,
private readonly _state: FilePreviewStateService,
protected readonly componentLogService: ComponentLogService,
) {
effect(async () => {
this._state.file();
@ -48,7 +43,6 @@ export class StructuredComponentManagementComponent implements OnInit {
async ngOnInit(): Promise<void> {
await this.#loadData();
this.displayedComponents$ = this.#displayedComponents$();
}
deselectLast() {
@ -61,7 +55,7 @@ export class StructuredComponentManagementComponent implements OnInit {
async revertOverride(originalKey: string) {
this._loadingService.start();
await firstValueFrom(
this._componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]),
this.componentLogService.revertOverride(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, [originalKey]),
);
await this.#loadData();
}
@ -69,29 +63,21 @@ export class StructuredComponentManagementComponent implements OnInit {
async overrideValue(componentLogEntry: IComponentLogEntry) {
this._loadingService.start();
await firstValueFrom(
this._componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry),
this.componentLogService.override(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId, componentLogEntry),
);
await this.#loadData();
}
#displayedComponents$() {
const componentLogFilters$ = this._filterService.getFilterModels$('componentLogFilters');
return combineLatest([this.componentLogData$, componentLogFilters$]).pipe(
map(([components, filters]) => this._componentLogFilterService.filterComponents(components, filters)),
);
}
async #loadData(): Promise<void> {
const componentLogData = await firstValueFrom(
this._componentLogService.getComponentLogData(
await firstValueFrom(
this.componentLogService.loadComponentLogData(
this.file.dossierTemplateId,
this.file.dossierId,
this.file.fileId,
this.dictionaries,
),
);
this.#computeFilters(componentLogData);
this.componentLogData.set(componentLogData);
this.#computeFilters(this.componentLogService.all);
this._loadingService.stop();
}

View File

@ -17,7 +17,15 @@ import {
LoadingService,
Toaster,
} from '@iqser/common-ui';
import { copyLocalStorageFiltersValues, FilterService, NestedFilter, processFilters } from '@iqser/common-ui/lib/filtering';
import {
copyLocalStorageFiltersValues,
Filter,
FilterService,
IFilter,
INestedFilter,
NestedFilter,
processFilters,
} from '@iqser/common-ui/lib/filtering';
import { AutoUnsubscribe, Bind, bool, List, OnAttach, OnDetach } from '@iqser/common-ui/lib/utils';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { ManualRedactionEntryTypes, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
@ -73,6 +81,7 @@ import { StructuredComponentManagementComponent } from './components/structured-
import { DocumentInfoService } from './services/document-info.service';
import { RectangleAnnotationDialog } from './dialogs/rectangle-annotation-dialog/rectangle-annotation-dialog.component';
import { ANNOTATION_ACTION_ICONS, ANNOTATION_ACTIONS } from './utils/constants';
import { ComponentLogService } from '@services/entity-services/component-log.service';
@Component({
templateUrl: './file-preview-screen.component.html',
@ -147,6 +156,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _multiSelectService: MultiSelectService,
private readonly _documentInfoService: DocumentInfoService,
private readonly _componentLogService: ComponentLogService,
) {
super();
effect(() => {
@ -197,6 +207,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this._documentInfoService.shown();
this.#updateViewerPosition();
});
effect(() => {
this.state.componentReferenceIds();
this.#rebuildFilters();
});
}
get changed() {
@ -538,8 +553,13 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
#rebuildFilters() {
const startTime = new Date().getTime();
const annotationFilters = this._annotationProcessingService.getAnnotationFilter();
let annotationFilters = this._annotationProcessingService.getAnnotationFilter();
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
if (this.isDocumine) {
annotationFilters = this.#filterAnnotationFilters(annotationFilters);
}
this._filterService.addFilterGroup({
slug: 'primaryFilters',
filterTemplate: this._filterTemplate,
@ -832,6 +852,38 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
};
}
#filterAnnotationFilters(annotationFilters: INestedFilter[]) {
const components = this._componentLogService.all;
const filteredComponentIds = untracked(this.state.componentReferenceIds);
if (filteredComponentIds && annotationFilters) {
const filteredComponentIdsSet = new Set(filteredComponentIds);
const references = new Set<string>();
for (const component of components) {
for (const componentValue of component.componentValues) {
for (const reference of componentValue.entityReferences) {
if (filteredComponentIdsSet.has(reference.id)) {
references.add(reference.type);
}
}
}
}
return annotationFilters
.map(filter => {
const filteredChildren = filter.children.filter(c => references.has(c.label.replace(/ /g, '_').toLowerCase()));
if (filteredChildren.length) {
return { ...filter, children: filteredChildren };
}
return null;
})
.filter(f => f !== null);
}
return annotationFilters;
}
#updateViewerPosition() {
if (this.isDocumine) {
if (this._documentInfoService.shown()) {

View File

@ -1,6 +1,6 @@
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
import { computed, effect, inject, Injectable, signal, Signal, WritableSignal } from '@angular/core';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { LoadingService, wipeCache } from '@iqser/common-ui';
import { getParam } from '@iqser/common-ui/lib/utils';
import { TranslateService } from '@ngx-translate/core';
@ -14,7 +14,7 @@ import { FilesMapService } from '@services/files/files-map.service';
import { FilesService } from '@services/files/files.service';
import { PermissionsService } from '@services/permissions.service';
import { NGXLogger } from 'ngx-logger';
import { BehaviorSubject, firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
import { firstValueFrom, from, merge, Observable, of, pairwise, Subject, switchMap } from 'rxjs';
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
import { ViewModeService } from './view-mode.service';
@ -42,7 +42,7 @@ export class FilePreviewStateService {
readonly dossierDictionary: Signal<Dictionary>;
readonly blob$: Observable<Blob>;
readonly componentReferenceIds$: Observable<string[] | null>;
readonly #componentReferenceIds$ = new BehaviorSubject<string[] | null>(null);
readonly componentReferenceIds = signal<string[]>([]);
readonly dossierId = getParam(DOSSIER_ID);
readonly dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly fileId = getParam(FILE_ID);
@ -64,7 +64,7 @@ export class FilePreviewStateService {
this.dossier = toSignal(dossiersServiceResolver().getEntityChanged$(this.dossierId));
this.file$ = inject(FilesMapService).watch$(this.dossierId, this.fileId);
this.file = toSignal(this.file$);
this.componentReferenceIds$ = this.#componentReferenceIds$.asObservable();
this.componentReferenceIds$ = toObservable(this.componentReferenceIds);
this.excludedPages = signal(this.file().excludedPages);
this.isWritable = computed(() => {
const isWritable = this._permissionsService.canPerformAnnotationActions(this.file(), this.dossier());
@ -94,10 +94,6 @@ export class FilePreviewStateService {
);
}
set componentReferenceIds(ids: string[]) {
this.#componentReferenceIds$.next(ids);
}
get dictionaries(): Dictionary[] {
const dictionaries = this._dictionariesMapService.get(this.dossierTemplateId);
if (this.dossierDictionary()) {
@ -134,10 +130,6 @@ export class FilePreviewStateService {
);
}
get componentReferenceIds() {
return this.#componentReferenceIds$.getValue();
}
reloadBlob(): void {
this.#reloadBlob$.next(true);
}

View File

@ -9,7 +9,7 @@ import { APP_BASE_HREF } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { firstValueFrom } from 'rxjs';
import { ComponentLogService } from '@services/files/component-log.service';
import { ComponentLogService } from '@services/entity-services/component-log.service';
@Component({
selector: 'redaction-file-download-btn',

View File

@ -1,15 +1,14 @@
import { Injectable } from '@angular/core';
import { GenericService } from '@iqser/common-ui';
import { catchError, map, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { saveAs } from 'file-saver';
import { EntitiesService } from '@iqser/common-ui';
import { ComponentDetails, ComponentLogEntry, Dictionary, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { mapEach } from '@common-ui/utils';
import { FilePreviewStateService } from '../../modules/file-preview/services/file-preview-state.service';
import { saveAs } from 'file-saver';
import { HttpHeaders } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ComponentLogService extends GenericService<void> {
export class ComponentLogService extends EntitiesService<IComponentLogEntry, ComponentLogEntry> {
protected readonly _defaultModelPath = '';
#componentLogRequest(
@ -35,7 +34,7 @@ export class ComponentLogService extends GenericService<void> {
);
}
getComponentLogData(
loadComponentLogData(
dossierTemplateId: string,
dossierId: string,
fileId: string,
@ -47,6 +46,7 @@ export class ComponentLogService extends GenericService<void> {
map(componentDetails => this.#mapComponentDetails(componentDetails)),
mapEach(log => new ComponentLogEntry(log)),
map(log => this.#updateDisplayValue(log, dictionaries)),
tap(log => this.setEntities(log)),
);
}

View File

@ -1,4 +1,5 @@
import { ComponentValue, IComponentValue } from './component-value';
import { IListable } from '@iqser/common-ui';
export type ComponentDetails = Record<string, Record<'componentValues', IComponentValue>>;
@ -9,16 +10,22 @@ export interface IComponentLogEntry {
overridden?: boolean;
}
export class ComponentLogEntry implements IComponentLogEntry {
export class ComponentLogEntry implements IComponentLogEntry, IListable {
readonly id: string;
readonly name: string;
readonly originalKey: string;
readonly componentValues: ComponentValue[];
readonly overridden: boolean;
constructor(entry: IComponentLogEntry) {
this.id = entry.name;
this.name = entry.name;
this.originalKey = entry.name;
this.componentValues = entry.componentValues;
this.overridden = !!entry.overridden;
}
get searchKey(): string {
return this.originalKey;
}
}

View File

@ -2,7 +2,6 @@ import {
IWatermark,
WATERMARK_HORIZONTAL_ALIGNMENTS,
WATERMARK_VERTICAL_ALIGNMENTS,
WatermarkAlignment,
WatermarkHorizontalAlignment,
WatermarkOrientation,
WatermarkVerticalAlignment,