Skip to content

[NAE-2047] Fulltext search and advanced search usability with multichoice caseRef #272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: release/6.5.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export abstract class AbstractCaseRefBaseFieldComponent<T extends DataField<unkn
}
let providers = [
{
provide: NAE_DEFAULT_HEADERS, useValue: this.dataField.component?.properties?.headers.split(',')
provide: NAE_DEFAULT_HEADERS, useValue: this.dataField.component?.properties?.headers?.split(',')
},
{
provide: NAE_CASE_REF_CREATE_CASE, useValue: this.dataField.component?.properties?.createCase === 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {HttpClientTestingModule} from '@angular/common/http/testing';
import {MatIconModule} from '@angular/material/icon';
import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing';
import {MockAuthenticationMethodService} from '../utility/tests/mocks/mock-authentication-method-service';
import {Component, Injector, Optional} from '@angular/core';
import {Component, Inject, Injector, Optional} from '@angular/core';
import {AbstractHeaderComponent} from './abstract-header.component';
import {TranslateLibModule} from '../translate/translate-lib.module';
import {RouterTestingModule} from '@angular/router/testing';
Expand All @@ -29,6 +29,13 @@ import {OverflowService} from './services/overflow.service';
import {AllowedNetsService} from '../allowed-nets/services/allowed-nets.service';
import {TestNoAllowedNetsFactory} from '../utility/tests/test-factory-methods';
import {AllowedNetsServiceFactory} from '../allowed-nets/services/factory/allowed-nets-service-factory';
import {CaseViewService} from "../view/case-view/service/case-view-service";
import {
DATA_FIELD_PORTAL_DATA,
DataFieldPortalData
} from "../data-fields/models/data-field-portal-data-injection-token";
import {MultichoiceField} from "../data-fields/multichoice-field/models/multichoice-field";
import {EnumerationField} from "../data-fields/enumeration-field/models/enumeration-field";

describe('AbstractHeaderComponent', () => {
let component: TestHeaderComponent;
Expand Down Expand Up @@ -89,7 +96,9 @@ describe('AbstractHeaderComponent', () => {
class TestHeaderComponent extends AbstractHeaderComponent {
constructor(protected _injector: Injector,
protected _translate: TranslateService,
@Optional() protected _overflowService: OverflowService) {
super(_injector, _translate, _overflowService);
@Optional() protected _overflowService: OverflowService,
@Optional() protected _caseViewService: CaseViewService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) protected _dataFieldPortalData: DataFieldPortalData<MultichoiceField | EnumerationField>) {
super(_injector, _translate, _overflowService, _caseViewService, _dataFieldPortalData);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, Injector, Input, OnDestroy, OnInit, Optional} from '@angular/core';
import {Component, Inject, Injector, Input, OnDestroy, OnInit, Optional} from '@angular/core';
import {AbstractHeaderService} from './abstract-header-service';
import {CaseHeaderService} from './case-header/case-header.service';
import {TaskHeaderService} from './task-header/task-header.service';
Expand All @@ -12,6 +12,13 @@ import {OverflowService} from './services/overflow.service';
import {stopPropagation} from '../utility/stop-propagation';
import {Subscription} from 'rxjs';
import {debounceTime} from "rxjs/operators";
import {CaseViewService} from "../view/case-view/service/case-view-service";
import {
DATA_FIELD_PORTAL_DATA,
DataFieldPortalData
} from "../data-fields/models/data-field-portal-data-injection-token";
import {MultichoiceField} from "../data-fields/multichoice-field/models/multichoice-field";
import {EnumerationField} from "../data-fields/enumeration-field/models/enumeration-field";

@Component({
selector: 'ncc-abstract-header',
Expand Down Expand Up @@ -45,11 +52,16 @@ export abstract class AbstractHeaderComponent implements OnInit, OnDestroy {
protected _initHeaderCount: number = undefined;
protected _initResponsiveHeaders: boolean = undefined;
protected _approvalFormControl: FormControl;
protected _changeValue: boolean;
protected _subCases: Subscription;

constructor(protected _injector: Injector,
protected _translate: TranslateService,
@Optional() protected _overflowService: OverflowService) {
@Optional() protected _overflowService: OverflowService,
@Optional() protected _caseViewService: CaseViewService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) protected _dataFieldPortalData: DataFieldPortalData<MultichoiceField | EnumerationField>) {
this.initializeFormControls(this._overflowService !== null);
this._changeValue = true;
}

@Input()
Expand Down Expand Up @@ -93,6 +105,7 @@ export abstract class AbstractHeaderComponent implements OnInit, OnDestroy {
this.headerService.responsiveHeaders = this._initResponsiveHeaders;
}
this.headerService.preferenceColumnCount$.subscribe(value => this.columnCountControl.setValue(value));
this.resolveApprovalDatafields();
}

ngOnDestroy(): void {
Expand All @@ -101,6 +114,9 @@ export abstract class AbstractHeaderComponent implements OnInit, OnDestroy {
this.subColumnCountControl.unsubscribe();
this.subOverflowControl.unsubscribe();
}
if (this._subCases) {
this._subCases.unsubscribe();
}
}

/**
Expand Down Expand Up @@ -199,4 +215,54 @@ export abstract class AbstractHeaderComponent implements OnInit, OnDestroy {
}
});
}

public indeterminate() {
if (this._caseViewService) {
return this._dataFieldPortalData?.dataField?.value?.length > 0 &&
this._caseViewService.cases.some(value => this._dataFieldPortalData?.dataField.value.includes(value.stringId)) &&
!this.resolveApprovalValue();
}
return this._dataFieldPortalData?.dataField?.value?.length > 0 &&
this._dataFieldPortalData?.dataField?.value?.length < this._dataFieldPortalData?.dataField?.choices?.length;
}

public typeApproval() {
return this._dataFieldPortalData?.dataField instanceof MultichoiceField ? 'multichoice' : 'enumeration';
}

protected resolveApprovalDatafields() {
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof MultichoiceField && this._caseViewService) {
this.approvalFormControl.setValue(this.resolveApprovalValue());
this.approvalFormControl.valueChanges.subscribe(value => {
if (this._changeValue) {
if (value) {
this._dataFieldPortalData.dataField.value = this._caseViewService.cases.map(caze => caze.stringId);
} else {
this._dataFieldPortalData.dataField.value = [];
}
}
this._changeValue = true;
})
this._dataFieldPortalData.dataField.valueChanges().subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
this._subCases = this._caseViewService.cases$.subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
}
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof EnumerationField) {
this.approvalFormControl.valueChanges.subscribe(value => {
this._dataFieldPortalData.dataField.value = null;
})
}
}

Comment on lines +233 to +260
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Improve null checks and clarify EnumerationField behavior.

  1. The null check should also handle undefined:
-if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof MultichoiceField && this._caseViewService) {
+if (this._dataFieldPortalData && this._dataFieldPortalData.dataField instanceof MultichoiceField && this._caseViewService) {
  1. For EnumerationField, the approval control always sets the field value to null. Is this the intended behavior? It seems destructive and might confuse users.

Consider splitting this method into separate handlers for each field type to improve readability and maintainability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected resolveApprovalDatafields() {
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof MultichoiceField && this._caseViewService) {
this.approvalFormControl.setValue(this.resolveApprovalValue());
this.approvalFormControl.valueChanges.subscribe(value => {
if (this._changeValue) {
if (value) {
this._dataFieldPortalData.dataField.value = this._caseViewService.cases.map(caze => caze.stringId);
} else {
this._dataFieldPortalData.dataField.value = [];
}
}
this._changeValue = true;
})
this._dataFieldPortalData.dataField.valueChanges().subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
this._subCases = this._caseViewService.cases$.subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
}
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof EnumerationField) {
this.approvalFormControl.valueChanges.subscribe(value => {
this._dataFieldPortalData.dataField.value = null;
})
}
}
protected resolveApprovalDatafields() {
if (this._dataFieldPortalData && this._dataFieldPortalData.dataField instanceof MultichoiceField && this._caseViewService) {
this.approvalFormControl.setValue(this.resolveApprovalValue());
this.approvalFormControl.valueChanges.subscribe(value => {
if (this._changeValue) {
if (value) {
this._dataFieldPortalData.dataField.value = this._caseViewService.cases.map(caze => caze.stringId);
} else {
this._dataFieldPortalData.dataField.value = [];
}
}
this._changeValue = true;
})
this._dataFieldPortalData.dataField.valueChanges().subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
this._subCases = this._caseViewService.cases$.subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this.resolveApprovalValue());
})
}
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof EnumerationField) {
this.approvalFormControl.valueChanges.subscribe(value => {
this._dataFieldPortalData.dataField.value = null;
})
}
}
🤖 Prompt for AI Agents
In projects/netgrif-components-core/src/lib/header/abstract-header.component.ts
around lines 233 to 260, update the null checks to also handle undefined values
for _dataFieldPortalData to make the checks more robust. Review the logic for
EnumerationField where approvalFormControl.valueChanges always sets the
dataField value to null, confirm if this is intended or if it should preserve or
update the value differently to avoid unintended data loss. Refactor the
resolveApprovalDatafields method by splitting the logic into separate handler
methods for MultichoiceField and EnumerationField to improve code readability
and maintainability.

protected resolveApprovalValue() {
if (this._caseViewService.cases.length === 0) {
return false;
}
return this._caseViewService.cases.every(value => this._dataFieldPortalData?.dataField.value.includes(value.stringId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export abstract class AbstractTaskListPaginationComponent extends AbstractDefaul
public length: number;
public pageSize = 20;
public pageIndex = 0;
public pageSizeOptions: Array<number> = [10, 20, 50];
public pageSizeOptions: Array<number> = [10, 20, 50, 100];

@Input() public disabled: boolean;
@Input()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export abstract class AbstractCaseListPaginatorComponent extends AbstractDefault
public length: number;
public pageSize = 20;
public pageIndex = 0;
public pageSizeOptions: number[] = [10, 20, 50];
public pageSizeOptions: number[] = [10, 20, 50, 100];
@Input() public approval: boolean;
@Input() public disabled: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On

protected _loading$: LoadingWithFilterEmitter;
protected _cases$: Observable<Array<Case>>;
protected _cases: Array<Case>;
protected _nextPage$: BehaviorSubject<PageLoadRequestContext>;
protected _endOfData: boolean;
protected _pagination: Pagination;
Expand All @@ -67,6 +68,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On
@Optional() @Inject(NAE_NEW_CASE_CONFIGURATION) newCaseConfig: NewCaseConfiguration,
protected _permissionService: PermissionService) {
super(resolver);
this._cases = [];
this._newCaseConfiguration = {...this.DEFAULT_NEW_CASE_CONFIGURATION};
if (newCaseConfig !== null) {
Object.assign(this._newCaseConfiguration, newCaseConfig);
Expand Down Expand Up @@ -112,7 +114,8 @@ export class CaseViewService extends AbstractSortableViewComponent implements On
}, {})
);
this._cases$ = casesMap.pipe(
map(v => Object.values(v))
map(v => Object.values(v) as Array<Case>),
tap(cases => this._cases = cases as Array<Case>),
);
}

Expand All @@ -134,6 +137,10 @@ export class CaseViewService extends AbstractSortableViewComponent implements On
return this._cases$;
}

public get cases(): Array<Case> {
return this._cases;
}

public get pagination(): Pagination {
return this._pagination;
}
Expand Down
46 changes: 5 additions & 41 deletions projects/netgrif-components/src/lib/header/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
HeaderSearchService,
TaskHeaderService,
WorkflowHeaderService,
OverflowService, MultichoiceField, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, EnumerationField
OverflowService, MultichoiceField, DATA_FIELD_PORTAL_DATA, DataFieldPortalData, EnumerationField, CaseViewService
} from '@netgrif/components-core';
import {TranslateService} from '@ngx-translate/core';

Expand All @@ -22,49 +22,13 @@ import {TranslateService} from '@ngx-translate/core';
CategoryFactory
]
})
export class HeaderComponent extends AbstractHeaderComponent implements OnInit {
protected _changeValue: boolean;
export class HeaderComponent extends AbstractHeaderComponent {

constructor(injector: Injector,
translate: TranslateService,
@Optional() overflowService: OverflowService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) protected _dataFieldPortalData: DataFieldPortalData<MultichoiceField>) {
super(injector, translate, overflowService);
this._changeValue = true;
}

public indeterminate() {
return this._dataFieldPortalData?.dataField?.value?.length > 0 &&
this._dataFieldPortalData?.dataField?.value?.length < this._dataFieldPortalData?.dataField?.choices?.length;
}

public typeApproval() {
return this._dataFieldPortalData?.dataField instanceof MultichoiceField ? 'multichoice' : 'enumeration';
}

ngOnInit() {
super.ngOnInit();
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof MultichoiceField) {
this.approvalFormControl.setValue(this._dataFieldPortalData?.dataField.value.length === this._dataFieldPortalData?.dataField.choices.length);
this.approvalFormControl.valueChanges.subscribe(value => {
if (this._changeValue) {
if (value) {
this._dataFieldPortalData.dataField.value = this._dataFieldPortalData?.dataField.choices.map(val => val.key);
} else {
this._dataFieldPortalData.dataField.value = [];
}
}
this._changeValue = true;
})
this._dataFieldPortalData.dataField.valueChanges().subscribe(() => {
this._changeValue = false;
this.approvalFormControl.setValue(this._dataFieldPortalData?.dataField.value.length === this._dataFieldPortalData?.dataField.choices.length);
})
}
if (this._dataFieldPortalData !== null && this._dataFieldPortalData.dataField instanceof EnumerationField) {
this.approvalFormControl.valueChanges.subscribe(value => {
this._dataFieldPortalData.dataField.value = null;
})
}
@Optional() protected _caseViewService: CaseViewService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) protected _dataFieldPortalData: DataFieldPortalData<MultichoiceField | EnumerationField>) {
super(injector, translate, overflowService, _caseViewService, _dataFieldPortalData);
}
}
Loading