From 92012bf22a22119cdf86570f810e135ff61f78b7 Mon Sep 17 00:00:00 2001 From: Bruno Reis Date: Thu, 19 May 2022 01:26:13 -0300 Subject: [PATCH] Multiple performance adjustments for app-users (#2563) (cherry picked from commit f8d02fe912c26449cf3b19fc32a674a16a1b8d52) --- .../common/js/security/PrincipalLoader.ts | 6 ++ .../admin/common/js/ui/grid/DataView.ts | 4 ++ .../common/js/ui/selector/DropdownGrid.ts | 4 ++ .../common/js/ui/selector/DropdownList.ts | 4 ++ .../selector/combobox/BaseLoaderComboBox.ts | 7 +- .../combobox/BaseSelectedOptionsView.ts | 34 ++++++++++ .../js/ui/selector/combobox/ComboBox.ts | 65 ++++++++++++------- .../selector/combobox/SelectedOptionsView.ts | 2 + .../admin/common/js/util/loader/BaseLoader.ts | 21 +++++- src/main/resources/i18n/common.properties | 5 ++ 10 files changed, 126 insertions(+), 26 deletions(-) diff --git a/src/main/resources/assets/admin/common/js/security/PrincipalLoader.ts b/src/main/resources/assets/admin/common/js/security/PrincipalLoader.ts index 257898f3c..59d191dad 100644 --- a/src/main/resources/assets/admin/common/js/security/PrincipalLoader.ts +++ b/src/main/resources/assets/admin/common/js/security/PrincipalLoader.ts @@ -6,6 +6,7 @@ import {IdProviderKey} from './IdProviderKey'; import {Principal} from './Principal'; import {PrincipalKey} from './PrincipalKey'; import {GetPrincipalsByKeysRequest} from './GetPrincipalsByKeysRequest'; +import {BaseLoader} from '../util/loader/BaseLoader'; export class PrincipalLoader extends PostLoader { @@ -65,6 +66,11 @@ export class PrincipalLoader return this.getRequest().isPartiallyLoaded(); } + setUseDataPreLoad(bool: boolean): PrincipalLoader { + super.setUseDataPreLoad(bool); + return this; + } + protected createRequest(): FindPrincipalsRequest { return new FindPrincipalsRequest().setSize(10); } diff --git a/src/main/resources/assets/admin/common/js/ui/grid/DataView.ts b/src/main/resources/assets/admin/common/js/ui/grid/DataView.ts index 6b90b1084..941e3dc7f 100644 --- a/src/main/resources/assets/admin/common/js/ui/grid/DataView.ts +++ b/src/main/resources/assets/admin/common/js/ui/grid/DataView.ts @@ -66,6 +66,10 @@ export class DataView { return this.slickDataView.getItems(); } + getItemsByIds(ids: string[]): T[] { + return ids.map(id => this.getItemById(id)); + } + getItemById(id: string): T { return this.slickDataView.getItemById(id); } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/DropdownGrid.ts b/src/main/resources/assets/admin/common/js/ui/selector/DropdownGrid.ts index e775b1bdc..02d77b892 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/DropdownGrid.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/DropdownGrid.ts @@ -177,6 +177,10 @@ export abstract class DropdownGrid { }); } + getOptionsByValues(values: string[]): Option[] { + return this.getGridData().getItemsByIds(values); + } + getOptionByValue(value: string): Option { return this.getGridData().getItemById(value); } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/DropdownList.ts b/src/main/resources/assets/admin/common/js/ui/selector/DropdownList.ts index 2044909c8..57dd9ffda 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/DropdownList.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/DropdownList.ts @@ -87,6 +87,10 @@ export class DropdownList { return this.dropdownGrid.getSelectedOptions(); } + getOptionsByValues(values: string[]): Option[] { + return this.dropdownGrid.getOptionsByValues(values); + } + getOptionByValue(value: string): Option { return this.dropdownGrid.getOptionByValue(value); } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseLoaderComboBox.ts b/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseLoaderComboBox.ts index 964202b28..c6926c2a4 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseLoaderComboBox.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseLoaderComboBox.ts @@ -2,6 +2,7 @@ import * as Q from 'q'; import {BaseLoader} from '../../../util/loader/BaseLoader'; import {StringHelper} from '../../../util/StringHelper'; import {ComboBox, ComboBoxConfig} from './ComboBox'; +import {LoadMask} from '../../mask/LoadMask'; export class BaseLoaderComboBox extends ComboBox { @@ -9,11 +10,14 @@ export class BaseLoaderComboBox public static debug: boolean = false; private loader: BaseLoader; private tempValue: string; + private mask: LoadMask; constructor(name: string, config: ComboBoxConfig) { super(name, config); this.addClass('loader-combobox'); + + this.mask = new LoadMask(this); } public setLoader(loader: BaseLoader) { @@ -71,6 +75,7 @@ export class BaseLoaderComboBox callback(); } } else { + this.mask.show(); if (BaseLoaderComboBox.debug) { console.debug(this.toString() + '.doWhenLoaded: waiting to be loaded'); } @@ -88,7 +93,7 @@ export class BaseLoaderComboBox this.loader.onLoadedData(singleLoadListener); if (this.loader.isNotStarted()) { - this.loader.preLoad(value); + this.loader.preLoad(value).then(() => { this.mask.hide(); }); } } } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseSelectedOptionsView.ts b/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseSelectedOptionsView.ts index f7f40f26d..717f3a7e0 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseSelectedOptionsView.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/combobox/BaseSelectedOptionsView.ts @@ -9,6 +9,8 @@ import {SelectedOption} from './SelectedOption'; import {SelectedOptionEvent} from './SelectedOptionEvent'; import {BaseSelectedOptionView, BaseSelectedOptionViewBuilder} from './BaseSelectedOptionView'; import {assertNotNull} from '../../../util/Assert'; +import {PEl} from '../../../dom/PEl'; +import {i18n} from '../../../util/Messages'; export class BaseSelectedOptionsView extends DivEl @@ -22,6 +24,7 @@ export class BaseSelectedOptionsView private optionAddedListeners: { (added: SelectedOptionEvent): void; }[] = []; private optionMovedListeners: { (moved: SelectedOption, fromIndex: number): void }[] = []; private editable: boolean = true; + static MAX_TO_APPEND: number = 100; constructor(className?: string) { super('selected-options' + (className ? ' ' + className : '')); @@ -70,6 +73,37 @@ export class BaseSelectedOptionsView return new SelectedOption(new BaseSelectedOptionView(builder), this.count()); } + /* Will mark all options as selected, but if there are more options than {MAX_TO_APPEND} + it'll append only {MAX_TO_APPEND} of them to the view in order to improve performance. */ + addOptions(options: Option[], silent: boolean = false, keyCode: number): boolean { + let result: boolean; + + if (this.maximumOccurrencesReached()) { return false; } + + if (options.length <= BaseSelectedOptionsView.MAX_TO_APPEND) { + result = options.every(option => this.addOption(option, silent, keyCode)); + } else { + + const selectedOptions: SelectedOption[] = options.map((option, index) => { + const selectedOption = this.createSelectedOption(option); + + if (index <= BaseSelectedOptionsView.MAX_TO_APPEND) { + const optionView = selectedOption.getOptionView(); + optionView.onRemoveClicked(() => this.removeOption(option)); + this.appendChild(optionView); + } + + return selectedOption; + }); + + this.list = selectedOptions; + + this.appendChild(new PEl('warning-truncated-users').setHtml(i18n('warning.optionsview.truncated'))); + } + + return result; + } + addOption(option: Option, silent: boolean = false, keyCode: number): boolean { if (this.isSelected(option) || this.maximumOccurrencesReached()) { diff --git a/src/main/resources/assets/admin/common/js/ui/selector/combobox/ComboBox.ts b/src/main/resources/assets/admin/common/js/ui/selector/combobox/ComboBox.ts index 2167ddb88..f8a0f167a 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/combobox/ComboBox.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/combobox/ComboBox.ts @@ -325,6 +325,10 @@ export class ComboBox return this.comboBoxDropdown.getOptions(); } + getOptionsByValues(values: string[]): Option[] { + return this.comboBoxDropdown.getOptionsByValues(values); + } + getOptionByValue(value: string): Option { return this.comboBoxDropdown.getOptionByValue(value); } @@ -405,18 +409,7 @@ export class ComboBox this.hideDropdown(); } - selectOption(option: Option, silent: boolean = false, keyCode: number = -1) { - assertNotNull(option, 'option cannot be null'); - if (this.isOptionSelected(option)) { - return; - } - - let added = this.selectedOptionsView.addOption(option, silent, keyCode); - if (!added) { - return; - } - - this.comboBoxDropdown.markSelections(this.getSelectedOptions()); + private selectOptionHelper(keyCode: number = -1) { this.hideDropdown(); this.addClass('followed-by-options'); @@ -433,9 +426,38 @@ export class ComboBox if (this.maximumOccurrencesReached() && this.hideComboBoxWhenMaxReached) { this.hide(); } + this.ignoreNextFocus = false; } + selectOptions(options: Option[], silent: boolean = false, keyCode: number = -1): void { + assertNotNull(options, 'options cannot be null'); + + const added = this.selectedOptionsView.addOptions(options, silent, keyCode); + + if (!added) { return; } + + this.comboBoxDropdown.markSelections(options); + + this.selectOptionHelper(keyCode); + } + + selectOption(option: Option, silent: boolean = false, keyCode: number = -1): void { + assertNotNull(option, 'option cannot be null'); + + if (this.isOptionSelected(option)) { + return; + } + + const added = this.selectedOptionsView.addOption(option, silent, keyCode); + + if (!added) { return; } + + this.comboBoxDropdown.markSelections(this.getSelectedOptions()); + + this.selectOptionHelper(keyCode); + } + isOptionSelected(option: Option): boolean { return this.selectedOptionsView.isSelected(option); } @@ -682,16 +704,16 @@ export class ComboBox this.clearSelection(false, false, true); } - let valueSetPromise; let optionIds = this.splitValues(value); let missingOptionIds = this.getMissingOptionsIds(optionIds); if (this.displayMissingSelectedOptions || this.removeMissingSelectedOptions && missingOptionIds.length > 0) { - valueSetPromise = this.selectExistingAndHandleMissing(optionIds, missingOptionIds); + this.selectExistingAndHandleMissing(optionIds, missingOptionIds) + .then((options) => { this.notifyValueLoaded(options); }); } else { - valueSetPromise = Q(this.selectExistingOptions(optionIds)); + const options = this.selectExistingOptions(optionIds); + this.notifyValueLoaded(options); } - valueSetPromise.done((options) => this.notifyValueLoaded(options)); } protected splitValues(value: string): string[] { @@ -799,14 +821,9 @@ export class ComboBox private selectExistingOptions(optionIds: string[]) { let selectedOptions = []; - - optionIds.forEach((val) => { - let option = this.getOptionByValue(val); - if (option != null) { - selectedOptions.push(option); - this.selectOption(option, true); - } - }); + const options = this.getOptionsByValues(optionIds); + this.selectOptions(options); + selectedOptions = options; return selectedOptions; } diff --git a/src/main/resources/assets/admin/common/js/ui/selector/combobox/SelectedOptionsView.ts b/src/main/resources/assets/admin/common/js/ui/selector/combobox/SelectedOptionsView.ts index 893b078f3..2a1f49b40 100644 --- a/src/main/resources/assets/admin/common/js/ui/selector/combobox/SelectedOptionsView.ts +++ b/src/main/resources/assets/admin/common/js/ui/selector/combobox/SelectedOptionsView.ts @@ -12,6 +12,8 @@ export interface SelectedOptionsView createSelectedOption(option: Option): SelectedOption; + addOptions(option: Option[], silent: boolean, keyCode: number): boolean; + addOption(option: Option, silent: boolean, keyCode: number): boolean; updateOption(option: Option, newOption: Option, silent?: boolean); diff --git a/src/main/resources/assets/admin/common/js/util/loader/BaseLoader.ts b/src/main/resources/assets/admin/common/js/util/loader/BaseLoader.ts index 87333349c..7f034c491 100644 --- a/src/main/resources/assets/admin/common/js/util/loader/BaseLoader.ts +++ b/src/main/resources/assets/admin/common/js/util/loader/BaseLoader.ts @@ -31,6 +31,8 @@ export class BaseLoader { private comparator: Comparator; + private useDataPreLoad: boolean = false; + constructor(request?: HttpRequest) { this.setRequest(request || this.createRequest()); } @@ -50,7 +52,15 @@ export class BaseLoader { preLoad(searchString: string = ''): Q.Promise { this.notifyLoadingData(false); - return this.sendPreLoadRequest(searchString) + let promise: Q.Promise; + + if (this.useDataPreLoad) { + promise = this.preLoadData(searchString); + } else { + promise = this.sendPreLoadRequest(searchString); + } + + return promise .then(this.handleLoadSuccess.bind(this, false)) .catch(this.handleLoadError.bind(this, false)) .finally(() => this.status = LoaderStatus.PRE_LOADED); @@ -173,6 +183,15 @@ export class BaseLoader { }); } + setUseDataPreLoad(bool: boolean): BaseLoader { + this.useDataPreLoad = bool; + return this; + } + + preLoadData(searchString: string): Q.Promise { + throw new Error('Must be implemented in deriving classes!'); + } + protected createRequest(): HttpRequest { throw new Error('Must be implemented in deriving classes!'); } diff --git a/src/main/resources/i18n/common.properties b/src/main/resources/i18n/common.properties index 42af44e3a..aa5ac1e6d 100644 --- a/src/main/resources/i18n/common.properties +++ b/src/main/resources/i18n/common.properties @@ -219,3 +219,8 @@ tooltip.filterPanel.show=Show Search Panel tooltip.filterPanel.hide=Hide Search Panel tooltip.header.expand=Click to expand tooltip.header.collapse=Click to collapse + +# +# Warnings +# +warning.optionsview.truncated = The list of users is truncated