Revert "LP#1851884: eg-fm-record-editor: avoid fetching all rows from linked table"
authorJason Stephenson <jason@sigio.com>
Fri, 15 Jul 2022 17:34:18 +0000 (13:34 -0400)
committerJason Stephenson <jason@sigio.com>
Fri, 15 Jul 2022 17:38:14 +0000 (13:38 -0400)
This reverts commit f2824b8457fa1e941053cdc7fa715ab21e6f58c9.

It causes the following error when building Angular:

    ERROR in src/app/share/fm-editor/fm-editor.component.ts:517:30 - error TS2339: Property 'linkedSearchConditions' does not exist on type 'FmFieldOptions'.

    517             if (fieldOptions.linkedSearchConditions) {
                                     ~~~~~~~~~~~~~~~~~~~~~~
    src/app/share/fm-editor/fm-editor.component.ts:518:51 - error TS2339: Property 'linkedSearchConditions' does not exist on type 'FmFieldOptions'.

    518                 field.idlBaseQuery = fieldOptions.linkedSearchConditions;

There does not appear to be any way to resolve this as the patch also
relies on other changes in rel_3_8 and rel_3_9 that are not present in
rel_3_7.  For these reasons, I am reverting the patch from rel_3_7.

Signed-off-by: Jason Stephenson <jason@sigio.com>
Open-ILS/src/eg2/src/app/core/idl.service.ts
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts

index 3741e93..662c735 100644 (file)
@@ -161,9 +161,7 @@ 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. As
-    // a last ditch fallback, if there's no selector but the primary
-    // key is a text field, use that.
+    // defined, use 'name' if it exists as a field on the class.
     getClassSelector(idlClass: string): string {
 
         if (idlClass) {
@@ -175,13 +173,6 @@ 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;
-                }
-
             }
         }
 
index ab5f36e..af9f5f0 100644 (file)
               </ng-container>
             </ng-container>
 
-            <ng-container *ngSwitchCase="'link'">
-              <eg-combobox
-                id="{{idPrefix}}-{{field.name}}" name="{{field.name}}"
-                placeholder="{{field.label}}..." i18n-placeholder 
-                [required]="field.isRequired()"
-                [idlClass]="field.class" [asyncSupportsEmptyTermClick]="true"
-                [idlBaseQuery]="field.idlBaseQuery"
-                [idlField]="field.selector"
-                [selectedId]="record[field.name]()"
-                (onChange)="record[field.name]($event ? $event.id : null)">
-              </eg-combobox>
-            </ng-container>
-
             <ng-container *ngSwitchCase="'list'">
               <eg-combobox
                 id="{{idPrefix}}-{{field.name}}" name="{{field.name}}"
index 5d40405..4ac2e2e 100644 (file)
@@ -479,10 +479,7 @@ export class FmRecordEditorComponent
             };
         }
 
-        if (fieldOptions.customTemplate) {
-            field.template = fieldOptions.customTemplate.template;
-            field.context = fieldOptions.customTemplate.context;
-        } else if (fieldOptions.customValues) {
+        if (fieldOptions.customValues) {
 
             field.linkedValues = fieldOptions.customValues;
 
@@ -514,11 +511,7 @@ export class FmRecordEditorComponent
 
         } else if (field.datatype === 'link') {
 
-            if (fieldOptions.linkedSearchConditions) {
-                field.idlBaseQuery = fieldOptions.linkedSearchConditions;
-            }
-            field.selector = fieldOptions.linkedSearchField ||
-                             this.idl.getClassSelector(field.class);
+            promise = this.wireUpCombobox(field);
 
         } else if (field.datatype === 'timestamp') {
             field.datetime = this.datetimeFieldsList.includes(field.name);
@@ -527,6 +520,11 @@ export class FmRecordEditorComponent
                 this.orgDefaultAllowedList.includes(field.name);
         }
 
+        if (fieldOptions.customTemplate) {
+            field.template = fieldOptions.customTemplate.template;
+            field.context = fieldOptions.customTemplate.context;
+        }
+
         if (fieldOptions.helpText) {
             field.helpText = fieldOptions.helpText;
             field.helpText.current().then(help => field.helpTextValue = help);
@@ -535,6 +533,70 @@ export class FmRecordEditorComponent
         return promise || Promise.resolve();
     }
 
+    wireUpCombobox(field: any): Promise<any> {
+
+        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) {
+            return this.pcrud.retrieveAll(field.class, {}, {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<ComboboxEntry> => {
+
+            const search = {};
+            const orderBy = {order_by: {}};
+            const idField = this.idl.classes[field.class].pkey || 'id';
+
+            search[selector] = {'ilike': `%${term}%`};
+            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 {
@@ -645,11 +707,7 @@ export class FmRecordEditorComponent
             return field.datatype;
         }
 
-        if (field.datatype === 'link') {
-            return 'link';
-        }
-
-        if (field.linkedValues) {
+        if (field.datatype === 'link' || field.linkedValues) {
             return 'list';
         }