From 3046260156e0a6b579725002bd0c3ee207babd7a Mon Sep 17 00:00:00 2001 From: Tomas Machalek Date: Mon, 4 Nov 2024 16:20:24 +0100 Subject: [PATCH 1/2] liveattrs auto-complete improvement - implicit cutoff ... ... to prevent "too many items error". TODO: this must be implemented also in sqlite_live_attributes (the interface is already updated but it has no effect) --- lib/plugin_types/live_attributes/__init__.py | 13 +++++-- lib/plugins/masm_live_attributes/__init__.py | 4 +-- .../sqlite_live_attributes/__init__.py | 2 +- lib/views/tt_select.py | 8 +++-- public/files/js/models/textTypes/actions.ts | 1 + public/files/js/models/textTypes/main.ts | 18 ++++++---- .../files/js/models/textTypes/selectionOps.ts | 6 ++-- .../files/js/plugins/liveAttributes/models.ts | 19 +++++++---- public/files/js/translations/messages.cs.json | 1 + public/files/js/translations/messages.en.json | 1 + public/files/js/translations/messages.sl.json | 1 + .../files/js/translations/messages.szl.json | 1 + public/files/js/types/textTypes.ts | 1 + public/files/js/views/textTypes/input.tsx | 34 ++++++++++++------- 14 files changed, 75 insertions(+), 35 deletions(-) diff --git a/lib/plugin_types/live_attributes/__init__.py b/lib/plugin_types/live_attributes/__init__.py index 524fa396e6..0e15b8da01 100644 --- a/lib/plugin_types/live_attributes/__init__.py +++ b/lib/plugin_types/live_attributes/__init__.py @@ -57,6 +57,7 @@ class AttrValuesResponse: attr_values: Optional[Dict[str, Union[AttrValue, Dict[str, int], int]]] = None aligned: Optional[List[str]] = None poscount: Optional[int] = None + applied_cutoff: Optional[int] = None error: Optional[str] = None details: Optional[List[str]] = None @@ -73,9 +74,15 @@ async def is_enabled_for(self, plugin_ctx: AbstractCorpusPluginCtx, corpora: Lis @abc.abstractmethod async def get_attr_values( - self, plugin_ctx: AbstractCorpusPluginCtx, corpus: AbstractKCorpus, attr_map: Dict[str, Union[str, List[str]]], - aligned_corpora: Optional[List[str]] = None, autocomplete_attr: Optional[str] = None, - limit_lists: bool = True) -> AttrValuesResponse: + self, + plugin_ctx: AbstractCorpusPluginCtx, + corpus: AbstractKCorpus, + attr_map: Dict[str, Union[str, List[str]]], + aligned_corpora: Optional[List[str]] = None, + autocomplete_attr: Optional[str] = None, + apply_cutoff: Optional[bool] = False, + limit_lists: bool = True + ) -> AttrValuesResponse: """ Find all the available values of remaining attributes according to the provided attr_map and aligned_corpora diff --git a/lib/plugins/masm_live_attributes/__init__.py b/lib/plugins/masm_live_attributes/__init__.py index dabf155cda..400c25926e 100644 --- a/lib/plugins/masm_live_attributes/__init__.py +++ b/lib/plugins/masm_live_attributes/__init__.py @@ -65,8 +65,8 @@ async def is_enabled_for(self, plugin_ctx, corpora): return bool((await self.corparch.get_corpus_info(plugin_ctx, corpora[0])).metadata.database) async def get_attr_values( - self, plugin_ctx, corpus, attr_map, aligned_corpora=None, autocomplete_attr=None, limit_lists=True): - json_body = {'attrs': attr_map} + self, plugin_ctx, corpus, attr_map, aligned_corpora=None, autocomplete_attr=None, apply_cutoff=False, limit_lists=True): + json_body = {'attrs': attr_map, 'applyCutoff': apply_cutoff} if aligned_corpora: json_body['aligned'] = aligned_corpora if autocomplete_attr: diff --git a/lib/plugins/sqlite_live_attributes/__init__.py b/lib/plugins/sqlite_live_attributes/__init__.py index 2e7b9e325f..2b81b07128 100644 --- a/lib/plugins/sqlite_live_attributes/__init__.py +++ b/lib/plugins/sqlite_live_attributes/__init__.py @@ -190,7 +190,7 @@ async def get_subc_size(self, plugin_ctx, corpora, attr_map): @cached async def get_attr_values( - self, plugin_ctx, corpus, attr_map, aligned_corpora=None, autocomplete_attr=None, limit_lists=True): + self, plugin_ctx, corpus, attr_map, aligned_corpora=None, autocomplete_attr=None, apply_cutoff=False, limit_lists=True): """ Finds all the available values of remaining attributes according to the provided attr_map and aligned_corpora diff --git a/lib/views/tt_select.py b/lib/views/tt_select.py index 681b0e3ce8..e08ebc1b78 100644 --- a/lib/views/tt_select.py +++ b/lib/views/tt_select.py @@ -53,8 +53,12 @@ async def attr_val_autocomplete(amodel: CorpusActionModel, req: KRequest, resp: aligned = json.loads(req.form.get('aligned', '[]')) with plugins.runtime.LIVE_ATTRIBUTES as lattr: return await lattr.get_attr_values( - amodel.plugin_ctx, corpus=amodel.corp, attr_map=attrs, - aligned_corpora=aligned, autocomplete_attr=req.form.get('patternAttr')) + amodel.plugin_ctx, + corpus=amodel.corp, + attr_map=attrs, + aligned_corpora=aligned, + autocomplete_attr=req.form.get('patternAttr'), + apply_cutoff=True) @bp.route('/fill_attrs', methods=['POST']) diff --git a/public/files/js/models/textTypes/actions.ts b/public/files/js/models/textTypes/actions.ts index ba03c6e530..06c92dd139 100644 --- a/public/files/js/models/textTypes/actions.ts +++ b/public/files/js/models/textTypes/actions.ts @@ -156,6 +156,7 @@ export class Actions { attrName:string; autoCompleteData:Array; filterData:SelectionFilterMap; + appliedCutoff:number|undefined; }> = { name: 'TT_ATTRIBUTE_TEXT_INPUT_AUTOCOMPLETE_REQUEST_DONE' }; diff --git a/public/files/js/models/textTypes/main.ts b/public/files/js/models/textTypes/main.ts index e4ffd76882..95c694a405 100644 --- a/public/files/js/models/textTypes/main.ts +++ b/public/files/js/models/textTypes/main.ts @@ -444,16 +444,18 @@ export class TextTypesModel extends StatefulModel Actions.AttributeTextInputAutocompleteRequestDone, _ => !this.readonlyMode, action => { - if (!action.error) { - this.changeState(state => { - state.busyAttributes[action.payload.attrName] = false; + this.changeState(state => { + state.busyAttributes[action.payload.attrName] = false; + if (!action.error) { this.setAutoComplete( state, action.payload.attrName, action.payload.autoCompleteData, + action.payload.appliedCutoff ); - }); - } else { + } + }); + if (action.error) { this.pluginApi.showMessage('error', action.error); } } @@ -1122,11 +1124,13 @@ export class TextTypesModel extends StatefulModel setAutoComplete( state:TextTypesModelState, attrName:string, - values:Array + values:Array, + cutoff:number|undefined ):void { const attrIdx = this.getAttributeIdx(state, attrName); if (attrIdx > -1) { - state.attributes[attrIdx] = TTSelOps.setAutoComplete(state.attributes[attrIdx], values); + state.attributes[attrIdx] = TTSelOps.setAutoComplete( + state.attributes[attrIdx], values, cutoff); } } diff --git a/public/files/js/models/textTypes/selectionOps.ts b/public/files/js/models/textTypes/selectionOps.ts index f718a87ec5..9f44dbb106 100644 --- a/public/files/js/models/textTypes/selectionOps.ts +++ b/public/files/js/models/textTypes/selectionOps.ts @@ -205,13 +205,15 @@ export class TTSelOps { static setAutoComplete( sel:TextTypes.AnyTTSelection, - values:Array + values:Array, + cutoff:number|undefined ):TextTypes.AnyTTSelection { if (sel.type === 'text') { return { ...sel, - autoCompleteHints: values + autoCompleteHints: values, + autocompleteCutoff: cutoff }; } else { diff --git a/public/files/js/plugins/liveAttributes/models.ts b/public/files/js/plugins/liveAttributes/models.ts index ed42ec6340..d8caa61a92 100644 --- a/public/files/js/plugins/liveAttributes/models.ts +++ b/public/files/js/plugins/liveAttributes/models.ts @@ -40,10 +40,11 @@ import { DownloadType } from '../../app/page'; interface ServerRefineResponse extends Kontext.AjaxResponse { - error?:string; aligned:Array; poscount:number; attr_values:TextTypes.ValueDomainsSizes; + applied_cutoff?:number; + error?:string; } interface ServerBibInfoResponse extends Kontext.AjaxResponse { @@ -380,15 +381,21 @@ export class LiveAttrsModel extends StatelessModel implemen autoCompleteData: List.map( v => ({ident: v[1], label: v[2]}), values - ) + ), + appliedCutoff: resp.applied_cutoff } }); } else { - dispatch({ - name: TTActions.AttributeTextInputAutocompleteRequestDone.name, - error: new Error('Did not recieve list of items but a summary instead') - }); + dispatch( + TTActions.AttributeTextInputAutocompleteRequestDone, + { + attrName: action.payload.attrName, + filterData: {}, + autoCompleteData: [] + }, + new Error(this.pluginApi.translate('ucnkLA__too_many_autocomplete_values')) + ); } }, error: err => { diff --git a/public/files/js/translations/messages.cs.json b/public/files/js/translations/messages.cs.json index bb8a0486c3..ff360b0c32 100644 --- a/public/files/js/translations/messages.cs.json +++ b/public/files/js/translations/messages.cs.json @@ -317,6 +317,7 @@ "query__tt_exact_value": "Přesná hodnota...", "query__tt_regexp_value_label": "Regulární výraz", "query__tt_negative_selection": "Negativní výběr", + "query__tt_too_many_autocomplete_items_{num_items}": "Příliš mnoho odpovídajících položek. Zobrazuji pouze {num_items, plural, one{první # záznam} few{první # záznamy} other{prvních # záznamů}}.", "query__span_from": "od", "query__span_to": "do", "query__include_kwic": "včetně KWIC", diff --git a/public/files/js/translations/messages.en.json b/public/files/js/translations/messages.en.json index 0a15dc1495..3371f46c71 100644 --- a/public/files/js/translations/messages.en.json +++ b/public/files/js/translations/messages.en.json @@ -317,6 +317,7 @@ "query__tt_exact_value": "Exact value...", "query__tt_regexp_value_label": "Regular expression", "query__tt_negative_selection": "Negative selection", + "query__tt_too_many_autocomplete_items_{num_items}": "Too many matching items. Showing only {num_items, plural, one{first # record} few{first # records} other{first # records}}.", "query__span_from": "From", "query__span_to": "To", "query__include_kwic": "Include KWIC", diff --git a/public/files/js/translations/messages.sl.json b/public/files/js/translations/messages.sl.json index 36bff1e5ce..f6da08ac75 100644 --- a/public/files/js/translations/messages.sl.json +++ b/public/files/js/translations/messages.sl.json @@ -311,6 +311,7 @@ "query__tt_exact_value": "Exact value... UNTRANSLATED", "query__tt_regexp_value_label": "Regular expression UNTRANSLATED", "query__tt_negative_selection": "Negative selection UNTRANSLATED", + "query__tt_too_many_autocomplete_items_{num_items}": "Too many matching items. Showing only {num_items, plural, one{first # record} few{first # records} other{first # records}}. UNTRANSLATED", "concview__invalid_page_num_err": "Neveljavna številka strani", "concview__click_for_details": "Kliknite za ogled podrobnosti", "concview__sort_jump_to": "Skoči na", diff --git a/public/files/js/translations/messages.szl.json b/public/files/js/translations/messages.szl.json index a2277b1ef7..ffd7204ae4 100644 --- a/public/files/js/translations/messages.szl.json +++ b/public/files/js/translations/messages.szl.json @@ -317,6 +317,7 @@ "query__tt_exact_value": "Exact value... UNTRANSLATED", "query__tt_regexp_value_label": "Regular expression UNTRANSLATED", "query__tt_negative_selection": "Negative selection UNTRANSLATED", + "query__tt_too_many_autocomplete_items_{num_items}": "Too many matching items. Showing only {num_items, plural, one{first # record} few{first # records} other{first # records}}. UNTRANSLATED", "query__span_from": "ôd", "query__span_to": "do", "query__include_kwic": "Użyj tyż KWIC", diff --git a/public/files/js/types/textTypes.ts b/public/files/js/types/textTypes.ts index 3c352a3225..ae8f45d709 100644 --- a/public/files/js/types/textTypes.ts +++ b/public/files/js/types/textTypes.ts @@ -114,6 +114,7 @@ export interface TextInputAttributeSelection extends BaseAttributeSelection { isInterval:boolean; isNumeric:boolean; autoCompleteHints:Array; + autocompleteCutoff:number|undefined; values:Array; // it supports appending values via a single text input textFieldValue:string; excludeSelection:boolean; diff --git a/public/files/js/views/textTypes/input.tsx b/public/files/js/views/textTypes/input.tsx index 9f98df35d4..9b1a2255c8 100644 --- a/public/files/js/views/textTypes/input.tsx +++ b/public/files/js/views/textTypes/input.tsx @@ -116,18 +116,28 @@ export function init(dispatcher:IActionDispatcher, he:Kontext.ComponentHelpers): render() { const data = TTSelOps.getAutoComplete(this.props.attrObj); return ( - + <> + + {this.props.attrObj.autocompleteCutoff > 0 ? +

+ {he.translate( + 'query__tt_too_many_autocomplete_items_{num_items}', + {num_items: this.props.attrObj.autocompleteCutoff})} +

: + null + } + ); } } From 4b076ce110a55349b616d5933006569d74da2169 Mon Sep 17 00:00:00 2001 From: Tomas Machalek Date: Mon, 4 Nov 2024 16:31:20 +0100 Subject: [PATCH 2/2] Fix - invalid state initialization --- public/files/js/models/textTypes/common.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/files/js/models/textTypes/common.ts b/public/files/js/models/textTypes/common.ts index ebb2433e57..fb0c0668ba 100644 --- a/public/files/js/models/textTypes/common.ts +++ b/public/files/js/models/textTypes/common.ts @@ -179,6 +179,7 @@ function createTextInputAttributeSelection( docLabel: attrItem.attr_doc_label }, autoCompleteHints: [], + autocompleteCutoff: undefined, values: List.map( value => ({ value, @@ -193,7 +194,7 @@ function createTextInputAttributeSelection( textFieldValue: '', excludeSelection, type: 'text', - metaInfo: null, + metaInfo: null }; } return { @@ -207,6 +208,7 @@ function createTextInputAttributeSelection( docLabel: attrItem.attr_doc_label }, autoCompleteHints: [], + autocompleteCutoff: undefined, values: [], definesSubcorpus, textFieldValue: '',