mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Performance fix: add paging for custom field select options (#10755)
This commit is contained in:
		| @@ -28,6 +28,16 @@ | ||||
|               </div> | ||||
|             } | ||||
|           </div> | ||||
|           @if (allSelectOptions.length > SELECT_OPTION_PAGE_SIZE) { | ||||
|             <ngb-pagination | ||||
|               class="d-flex justify-content-end" | ||||
|               [pageSize]="SELECT_OPTION_PAGE_SIZE" | ||||
|               [collectionSize]="allSelectOptions.length" | ||||
|               [(page)]="selectOptionsPage" | ||||
|               [maxSize]="5" | ||||
|               size="sm" | ||||
|             ></ngb-pagination> | ||||
|           } | ||||
|           @if (object?.id) { | ||||
|             <small class="d-block mt-2" i18n>Warning: existing instances of this field will retain their current value index (e.g. option #1, #2, #3) after editing the options here</small> | ||||
|           } | ||||
|   | ||||
| @@ -125,4 +125,42 @@ describe('CustomFieldEditDialogComponent', () => { | ||||
|     fixture.detectChanges() | ||||
|     expect(document.activeElement).toBe(selectOptionInputs.last.nativeElement) | ||||
|   }) | ||||
|  | ||||
|   it('should send all select options including those changed in form on save', () => { | ||||
|     component.dialogMode = EditDialogMode.EDIT | ||||
|     component.object = { | ||||
|       id: 1, | ||||
|       name: 'Field 1', | ||||
|       data_type: CustomFieldDataType.Select, | ||||
|       extra_data: { | ||||
|         select_options: Array.from({ length: 50 }, (_, i) => ({ | ||||
|           label: `Option ${i + 1}`, | ||||
|           id: `${i + 1}-xyz`, | ||||
|         })), | ||||
|       }, | ||||
|     } | ||||
|     fixture.detectChanges() | ||||
|     component.ngOnInit() | ||||
|     component.selectOptionsPage = 2 | ||||
|     fixture.detectChanges() | ||||
|     component.objectForm | ||||
|       .get('extra_data') | ||||
|       .get('select_options') | ||||
|       .get('0') | ||||
|       .get('label') | ||||
|       .setValue('Updated Option 9') | ||||
|     const formValues = (component as any).getFormValues() | ||||
|     // first item unchanged | ||||
|     expect(formValues.extra_data.select_options[0]).toEqual({ | ||||
|       label: 'Option 1', | ||||
|       id: '1-xyz', | ||||
|     }) | ||||
|     // page 2 first item updated | ||||
|     expect( | ||||
|       formValues.extra_data.select_options[component.SELECT_OPTION_PAGE_SIZE] | ||||
|     ).toEqual({ | ||||
|       label: 'Updated Option 9', | ||||
|       id: '9-xyz', | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import { | ||||
|   FormsModule, | ||||
|   ReactiveFormsModule, | ||||
| } from '@angular/forms' | ||||
| import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' | ||||
| import { takeUntil } from 'rxjs' | ||||
| import { | ||||
| @@ -28,6 +29,8 @@ import { SelectComponent } from '../../input/select/select.component' | ||||
| import { TextComponent } from '../../input/text/text.component' | ||||
| import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component' | ||||
|  | ||||
| const SELECT_OPTION_PAGE_SIZE = 8 | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-custom-field-edit-dialog', | ||||
|   templateUrl: './custom-field-edit-dialog.component.html', | ||||
| @@ -37,6 +40,7 @@ import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component' | ||||
|     TextComponent, | ||||
|     FormsModule, | ||||
|     ReactiveFormsModule, | ||||
|     NgbPaginationModule, | ||||
|     NgxBootstrapIconsModule, | ||||
|   ], | ||||
| }) | ||||
| @@ -45,6 +49,21 @@ export class CustomFieldEditDialogComponent | ||||
|   implements OnInit, AfterViewInit | ||||
| { | ||||
|   CustomFieldDataType = CustomFieldDataType | ||||
|   SELECT_OPTION_PAGE_SIZE = SELECT_OPTION_PAGE_SIZE | ||||
|  | ||||
|   private _allSelectOptions: any[] = [] | ||||
|   public get allSelectOptions(): any[] { | ||||
|     return this._allSelectOptions | ||||
|   } | ||||
|  | ||||
|   private _selectOptionsPage: number | ||||
|   public get selectOptionsPage(): number { | ||||
|     return this._selectOptionsPage | ||||
|   } | ||||
|   public set selectOptionsPage(v: number) { | ||||
|     this._selectOptionsPage = v | ||||
|     this.updateSelectOptions() | ||||
|   } | ||||
|  | ||||
|   @ViewChildren('selectOption') | ||||
|   private selectOptionInputs: QueryList<ElementRef> | ||||
| @@ -67,17 +86,10 @@ export class CustomFieldEditDialogComponent | ||||
|       this.objectForm.get('data_type').disable() | ||||
|     } | ||||
|     if (this.object?.data_type === CustomFieldDataType.Select) { | ||||
|       this.selectOptions.clear() | ||||
|       this.object.extra_data.select_options | ||||
|         .filter((option) => option) | ||||
|         .forEach((option) => | ||||
|           this.selectOptions.push( | ||||
|             new FormGroup({ | ||||
|               label: new FormControl(option.label), | ||||
|               id: new FormControl(option.id), | ||||
|             }) | ||||
|           ) | ||||
|         ) | ||||
|       this._allSelectOptions = [ | ||||
|         ...(this.object.extra_data.select_options ?? []), | ||||
|       ] | ||||
|       this.selectOptionsPage = 1 | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -87,6 +99,19 @@ export class CustomFieldEditDialogComponent | ||||
|       .subscribe(() => { | ||||
|         this.selectOptionInputs.last?.nativeElement.focus() | ||||
|       }) | ||||
|  | ||||
|     this.objectForm.valueChanges | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe((change) => { | ||||
|         // Update the relevant select options values if changed in the form, which is only a page of the entire list | ||||
|         this.objectForm | ||||
|           .get('extra_data.select_options') | ||||
|           ?.value.forEach((option, index) => { | ||||
|             this._allSelectOptions[ | ||||
|               index + (this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE | ||||
|             ] = option | ||||
|           }) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
| @@ -108,6 +133,17 @@ export class CustomFieldEditDialogComponent | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   protected getFormValues() { | ||||
|     const formValues = super.getFormValues() | ||||
|     if ( | ||||
|       this.objectForm.get('data_type')?.value === CustomFieldDataType.Select | ||||
|     ) { | ||||
|       // Make sure we send all select options, with updated values | ||||
|       formValues.extra_data.select_options = this._allSelectOptions | ||||
|     } | ||||
|     return formValues | ||||
|   } | ||||
|  | ||||
|   getDataTypes() { | ||||
|     return DATA_TYPE_LABELS | ||||
|   } | ||||
| @@ -116,13 +152,35 @@ export class CustomFieldEditDialogComponent | ||||
|     return this.dialogMode === EditDialogMode.EDIT | ||||
|   } | ||||
|  | ||||
|   private updateSelectOptions() { | ||||
|     this.selectOptions.clear() | ||||
|     this._allSelectOptions | ||||
|       .slice( | ||||
|         (this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE, | ||||
|         this.selectOptionsPage * SELECT_OPTION_PAGE_SIZE | ||||
|       ) | ||||
|       .forEach((option) => | ||||
|         this.selectOptions.push( | ||||
|           new FormGroup({ | ||||
|             label: new FormControl(option.label), | ||||
|             id: new FormControl(option.id), | ||||
|           }) | ||||
|         ) | ||||
|       ) | ||||
|   } | ||||
|  | ||||
|   public addSelectOption() { | ||||
|     this.selectOptions.push( | ||||
|       new FormGroup({ label: new FormControl(null), id: new FormControl(null) }) | ||||
|     this._allSelectOptions.push({ label: null, id: null }) | ||||
|     this.selectOptionsPage = Math.ceil( | ||||
|       this.allSelectOptions.length / SELECT_OPTION_PAGE_SIZE | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   public removeSelectOption(index: number) { | ||||
|     this.selectOptions.removeAt(index) | ||||
|     this._allSelectOptions.splice( | ||||
|       index + (this.selectOptionsPage - 1) * SELECT_OPTION_PAGE_SIZE, | ||||
|       1 | ||||
|     ) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -147,9 +147,13 @@ export abstract class EditDialogComponent< | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   protected getFormValues(): any { | ||||
|     return Object.assign({}, this.objectForm.value) | ||||
|   } | ||||
|  | ||||
|   save() { | ||||
|     this.error = null | ||||
|     const formValues = Object.assign({}, this.objectForm.value) | ||||
|     const formValues = this.getFormValues() | ||||
|     const permissionsObject: PermissionsFormObject = | ||||
|       this.objectForm.get('permissions_form')?.value | ||||
|     if (permissionsObject) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon