From 11e7e336513edd105eaa4e3e43f5426a28b10a14 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Wed, 27 Apr 2022 20:42:05 -0400 Subject: [PATCH] LP#1851884: eg-fm-record-editor: avoid fetching all rows from linked table This patch ensures that Angular FM record editor dialogs do not attempt to fetch all rows from target table when constructing a combobox for a linked field. In particular, it makes the following changes: * If a custom template for a field is supplied, use that by default rather than _also_ creating a data source for a combobox. * Rather than creating its own data source for a linked field, the FM record editor now uses the IDL mode of eg-combobox. By doing this, we use eg-combobox's default data source, which limits record retrievals to 100 rows max. Also, empty-click is now enabled by default. * When attempting to identify a selector for an IDL class, if the class doesn't define a selector and doesn't have a field named 'name', but its primary key is a text field, use the primary key as the selector. To test ------- [1] Create a few thousand empty bib record buckets in your test database. [2] Edit a carousel under Local Administration; note that it can take some time for the dialog to load. [3] Apply the patch and repeat step 2. This time, the carousel edit modal should open instantly. [4] Test various Angular record editor modals to confirm that they behave as expected. For example: * The allocate to fund dialog in Fund Administration, in particular to verify that only active funds show up. * The assign user to course modal, to verify that searching is done by course number, not name. * Filter Dialog Set editor under /eg2/en-US/staff/admin/local/config/filter_dialog_filter_set to verify that the drop down for the creator doesn't fetch all patrons. (Note that this is an artificial example). Signed-off-by: Galen Charlton Signed-off-by: Tiffany Little --- Open-ILS/src/eg2/src/app/core/idl.service.ts | 11 +- .../app/share/fm-editor/fm-editor.component.html | 13 +++ .../src/app/share/fm-editor/fm-editor.component.ts | 119 +++------------------ 3 files changed, 37 insertions(+), 106 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/core/idl.service.ts b/Open-ILS/src/eg2/src/app/core/idl.service.ts index 662c73586b..3741e930e3 100644 --- a/Open-ILS/src/eg2/src/app/core/idl.service.ts +++ b/Open-ILS/src/eg2/src/app/core/idl.service.ts @@ -161,7 +161,9 @@ export class IdlService { } // Return the selector field for the class. If no selector is - // defined, use 'name' if it exists as a field on the class. + // defined, use 'name' if it exists as a field on the class. As + // a last ditch fallback, if there's no selector but the primary + // key is a text field, use that. getClassSelector(idlClass: string): string { if (idlClass) { @@ -173,6 +175,13 @@ export class IdlService { // No selector defined in the IDL, try 'name'. if ('name' in classDef.field_map) { return 'name'; } + + // last ditch - if the primary key is a text field, + // treat it as the selector + if (classDef.field_map[classDef.pkey].datatype === 'text') { + return classDef.pkey; + } + } } diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html index 859dc0b6c4..deda3e54aa 100644 --- a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html @@ -161,6 +161,19 @@ + + + + + field.helpTextValue = help); @@ -579,103 +581,6 @@ export class FmRecordEditorComponent return promise || Promise.resolve(); } - wireUpCombobox(field: any): Promise { - - const fieldOptions = this.fieldOptions[field.name] || {}; - - // globally preloading unless a field-specific value is set. - if (this.preloadLinkedValues) { - if (!('preloadLinkedValues' in fieldOptions)) { - fieldOptions.preloadLinkedValues = true; - } - } - - const selector = fieldOptions.linkedSearchField || - this.idl.getClassSelector(field.class); - - if (!selector && !fieldOptions.preloadLinkedValues) { - // User probably expects an async data source, but we can't - // provide one without a selector. Warn the user. - console.warn(`Class ${field.class} has no selector. - Pre-fetching all rows for combobox`); - } - - if (fieldOptions.preloadLinkedValues || !selector) { - const search = {}; - const orderBy = {order_by: {}}; - if (selector) { - orderBy.order_by[field.class] = selector; - } - const idField = this.idl.classes[field.class].pkey || 'id'; - search[idField] = {'!=' : null}; - if (fieldOptions.linkedSearchConditions) { - const conditions = {}; - Object.keys(fieldOptions.linkedSearchConditions).forEach(key => { - conditions[key] = fieldOptions.linkedSearchConditions[key]; - }); - // ensure that the current value, if present, is included - // in case it doesn't otherwise meet the conditions - const linkedValue = this.record[field.name](); - if (linkedValue !== null && linkedValue !== undefined) { - search['-or'] = []; - const retrieveRec = {}; - retrieveRec[idField] = linkedValue; - search['-or'].push(retrieveRec); - search['-or'].push(conditions); - } else { - // just tack on the conditions - Object.keys(conditions).forEach(key => { - search[key] = conditions[key]; - }); - } - } - return this.pcrud.search(field.class, search, orderBy, {atomic : true}) - .toPromise().then(list => { - field.linkedValues = - this.flattenLinkedValues(field, list); - }); - } - - // If we have a selector, wire up for async data retrieval - field.linkedValuesSource = - (term: string): Observable => { - - const search = {}; - const orderBy = {order_by: {}}; - const idField = this.idl.classes[field.class].pkey || 'id'; - - search[selector] = {'ilike': `%${term}%`}; - if (fieldOptions.linkedSearchConditions) { - Object.keys(fieldOptions.linkedSearchConditions).forEach(key => { - search[key] = fieldOptions.linkedSearchConditions[key]; - }); - } - orderBy.order_by[field.class] = selector; - - return this.pcrud.search(field.class, search, orderBy) - .pipe(map(idlThing => - // Map each object into a ComboboxEntry upon arrival - this.flattenLinkedValues(field, [idlThing])[0] - )); - }; - - // Using an async data source, but a value is already set - // on the field. Fetch the linked object and add it to the - // combobox entry list so it will be avilable for display - // at dialog load time. - const linkVal = this.record[field.name](); - if (linkVal !== null && linkVal !== undefined) { - return this.pcrud.retrieve(field.class, linkVal).toPromise() - .then(idlThing => { - field.linkedValues = - this.flattenLinkedValues(field, Array(idlThing)); - }); - } - - // No linked value applied, nothing to pre-fetch. - return Promise.resolve(); - } - // Returns a context object to be inserted into a custom // field template. customTemplateFieldContext(fieldDef: any): CustomFieldContext { @@ -786,7 +691,11 @@ export class FmRecordEditorComponent return field.datatype; } - if (field.datatype === 'link' || field.linkedValues) { + if (field.datatype === 'link') { + return 'link'; + } + + if (field.linkedValues) { return 'list'; } -- 2.11.0