mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 19:17:13 -05:00 
			
		
		
		
	Performance fix: add paging for custom field select options (#10755)
This commit is contained in:
		
							parent
							
								
									9463a8fd26
								
							
						
					
					
						commit
						80595899c1
					
				@ -28,6 +28,16 @@
 | 
				
			|||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          </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) {
 | 
					          @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>
 | 
					            <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()
 | 
					    fixture.detectChanges()
 | 
				
			||||||
    expect(document.activeElement).toBe(selectOptionInputs.last.nativeElement)
 | 
					    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,
 | 
					  FormsModule,
 | 
				
			||||||
  ReactiveFormsModule,
 | 
					  ReactiveFormsModule,
 | 
				
			||||||
} from '@angular/forms'
 | 
					} from '@angular/forms'
 | 
				
			||||||
 | 
					import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'
 | 
				
			||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 | 
					import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
 | 
				
			||||||
import { takeUntil } from 'rxjs'
 | 
					import { takeUntil } from 'rxjs'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -28,6 +29,8 @@ import { SelectComponent } from '../../input/select/select.component'
 | 
				
			|||||||
import { TextComponent } from '../../input/text/text.component'
 | 
					import { TextComponent } from '../../input/text/text.component'
 | 
				
			||||||
import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
 | 
					import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SELECT_OPTION_PAGE_SIZE = 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'pngx-custom-field-edit-dialog',
 | 
					  selector: 'pngx-custom-field-edit-dialog',
 | 
				
			||||||
  templateUrl: './custom-field-edit-dialog.component.html',
 | 
					  templateUrl: './custom-field-edit-dialog.component.html',
 | 
				
			||||||
@ -37,6 +40,7 @@ import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
 | 
				
			|||||||
    TextComponent,
 | 
					    TextComponent,
 | 
				
			||||||
    FormsModule,
 | 
					    FormsModule,
 | 
				
			||||||
    ReactiveFormsModule,
 | 
					    ReactiveFormsModule,
 | 
				
			||||||
 | 
					    NgbPaginationModule,
 | 
				
			||||||
    NgxBootstrapIconsModule,
 | 
					    NgxBootstrapIconsModule,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
@ -45,6 +49,21 @@ export class CustomFieldEditDialogComponent
 | 
				
			|||||||
  implements OnInit, AfterViewInit
 | 
					  implements OnInit, AfterViewInit
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  CustomFieldDataType = CustomFieldDataType
 | 
					  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')
 | 
					  @ViewChildren('selectOption')
 | 
				
			||||||
  private selectOptionInputs: QueryList<ElementRef>
 | 
					  private selectOptionInputs: QueryList<ElementRef>
 | 
				
			||||||
@ -67,17 +86,10 @@ export class CustomFieldEditDialogComponent
 | 
				
			|||||||
      this.objectForm.get('data_type').disable()
 | 
					      this.objectForm.get('data_type').disable()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.object?.data_type === CustomFieldDataType.Select) {
 | 
					    if (this.object?.data_type === CustomFieldDataType.Select) {
 | 
				
			||||||
      this.selectOptions.clear()
 | 
					      this._allSelectOptions = [
 | 
				
			||||||
      this.object.extra_data.select_options
 | 
					        ...(this.object.extra_data.select_options ?? []),
 | 
				
			||||||
        .filter((option) => option)
 | 
					      ]
 | 
				
			||||||
        .forEach((option) =>
 | 
					      this.selectOptionsPage = 1
 | 
				
			||||||
          this.selectOptions.push(
 | 
					 | 
				
			||||||
            new FormGroup({
 | 
					 | 
				
			||||||
              label: new FormControl(option.label),
 | 
					 | 
				
			||||||
              id: new FormControl(option.id),
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,6 +99,19 @@ export class CustomFieldEditDialogComponent
 | 
				
			|||||||
      .subscribe(() => {
 | 
					      .subscribe(() => {
 | 
				
			||||||
        this.selectOptionInputs.last?.nativeElement.focus()
 | 
					        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() {
 | 
					  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() {
 | 
					  getDataTypes() {
 | 
				
			||||||
    return DATA_TYPE_LABELS
 | 
					    return DATA_TYPE_LABELS
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -116,13 +152,35 @@ export class CustomFieldEditDialogComponent
 | 
				
			|||||||
    return this.dialogMode === EditDialogMode.EDIT
 | 
					    return this.dialogMode === EditDialogMode.EDIT
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public addSelectOption() {
 | 
					  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(
 | 
					        this.selectOptions.push(
 | 
				
			||||||
      new FormGroup({ label: new FormControl(null), id: new FormControl(null) })
 | 
					          new FormGroup({
 | 
				
			||||||
 | 
					            label: new FormControl(option.label),
 | 
				
			||||||
 | 
					            id: new FormControl(option.id),
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public addSelectOption() {
 | 
				
			||||||
 | 
					    this._allSelectOptions.push({ label: null, id: null })
 | 
				
			||||||
 | 
					    this.selectOptionsPage = Math.ceil(
 | 
				
			||||||
 | 
					      this.allSelectOptions.length / SELECT_OPTION_PAGE_SIZE
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public removeSelectOption(index: number) {
 | 
					  public removeSelectOption(index: number) {
 | 
				
			||||||
    this.selectOptions.removeAt(index)
 | 
					    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() {
 | 
					  save() {
 | 
				
			||||||
    this.error = null
 | 
					    this.error = null
 | 
				
			||||||
    const formValues = Object.assign({}, this.objectForm.value)
 | 
					    const formValues = this.getFormValues()
 | 
				
			||||||
    const permissionsObject: PermissionsFormObject =
 | 
					    const permissionsObject: PermissionsFormObject =
 | 
				
			||||||
      this.objectForm.get('permissions_form')?.value
 | 
					      this.objectForm.get('permissions_form')?.value
 | 
				
			||||||
    if (permissionsObject) {
 | 
					    if (permissionsObject) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user