Skip to content

Commit

Permalink
UBO-348 large rfc5646 classification makes editor slow (#408)
Browse files Browse the repository at this point in the history
* fix slow editor by optimizing dom and server side processing

*  Update permissions

*  added placeholder

* fix async issues

* accept promise also after it was resolved
  • Loading branch information
sebhofmann authored Aug 5, 2024
1 parent 7a95258 commit 4347fa2
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 28 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ ubo-cli/target/bin/ubo.sh update permission read for id default with rulefile ub
ubo-cli/target/bin/ubo.sh update permission writedb for id default with rulefile ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
ubo-cli/target/bin/ubo.sh update permission deletedb for id default with rulefile ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
ubo-cli/target/bin/ubo.sh update permission read for id restapi:/ with rulefile ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
ubo-cli/target/bin/ubo.sh update permission read for id restapi:/classifications with rulefile ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
```

## MyCoRe-Solr-Configuration
Expand Down
13 changes: 7 additions & 6 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,13 @@ function setUpMyCoRe {
sed -ri "s/<mapping-file>META-INF\/mycore-ifs-mappings.xml<\/mapping-file>//" "${PERSISTENCE_XML}"
/opt/ubo/ubo-cli/target/bin/ubo.sh init superuser
/opt/ubo/ubo-cli/target/bin/ubo.sh update all classifications from directory /opt/ubo/ubo-cli/src/main/setup/classifications
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission create-mods for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission create-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission administrate-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission read for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission writedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission update permission read for id restapi:/ with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission create-mods for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission create-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission administrate-users for id POOLPRIVILEGE with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission writedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id restapi:/ with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission read for id restapi:/classifications with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-always-allowed.xml described by always allowed
/opt/ubo/ubo-cli/target/bin/ubo.sh update permission deletedb for id default with rulefile /opt/ubo/ubo-cli/src/main/setup/acl/acl-rule-administrators-only.xml described by administrators only
/opt/ubo/ubo-cli/target/bin/ubo.sh reload solr configuration main in core main
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
<xed:include uri="webapp:import-editor.xed" ref="fix.structure.parent" />
<xed:include uri="webapp:import-editor.xed" ref="cleanup" />
<xed:include uri="webapp:import-editor.xed" ref="cancel.submit" />
<xed:include uri="webapp:import-editor.xed" ref="javascript" />
</xed:template>
</xed:form>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@

<xed:include uri="webapp:import-editor.xed" ref="cleanup" />
<xed:include uri="webapp:import-editor.xed" ref="cancel.submit" />

<xed:include uri="webapp:import-editor.xed" ref="javascript" />
</xed:bind>

</xed:form>
Expand Down
37 changes: 16 additions & 21 deletions ubo-common/src/main/resources/META-INF/resources/import-editor.xed
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
]]>
</script>
</xed:template>

<xed:template id="javascript">
<script type="text/javascript" src="{$WebApplicationBaseURL}js/language-search.js"></script>
</xed:template>

<xed:template id="displayValidationMessages">
<xed:if test="$xed-validation-failed">
Expand Down Expand Up @@ -264,13 +268,10 @@
</xed:bind>
<span class="mx-2 align-self-center"> in </span>
<xed:bind xpath="@xml:lang">
<select class="mycore-form-input-double custom-select">
<option value=""><xed:output i18n="search.select" /></option>
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:de" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:en" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:fr" />
<xed:include uri="xslStyle:items2options:classification:editor:-1:children:rfc5646" />
</select>
<div class="language-search d-inline" data-preferred-languages="de,en,fr">
<input id="{xed:generate-id(..)}" type="hidden" class="language-search-input"/>
<!-- content is added by javascript -->
</div>
</xed:bind>
</div>
<xed:controls />
Expand Down Expand Up @@ -781,13 +782,10 @@
<xed:output value="concat(i18n:translate('ubo.language'), ': ')" />
</label>
<xed:bind xpath="mods:languageTerm[@type='code'][@authority='rfc5646']">
<select id="{xed:generate-id(..)}" class="autocomplete mycore-form-input custom-select">
<option value=""><xed:output i18n="search.select" /></option>
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:de" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:en" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:fr" />
<xed:include uri="xslStyle:items2options:classification:editor:-1:children:rfc5646" />
</select>
<div class="language-search" data-preferred-languages="de,en,fr">
<input id="{xed:generate-id()}" type="hidden" class="language-search-input"/>
<!-- content is added by javascript -->
</div>
</xed:bind>
<xed:controls />
</div>
Expand All @@ -801,13 +799,10 @@
<label for="{xed:generate-id()}" class="mycore-form-label">
<xed:output value="concat(i18n:translate('ubo.abstract.in'), ': ')" />
</label>
<select class="mycore-form-input custom-select">
<option value=""><xed:output i18n="search.select" /></option>
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:de" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:en" />
<xed:include uri="xslStyle:items2options:classification:editor:0:parents:rfc5646:fr" />
<xed:include uri="xslStyle:items2options:classification:editor:-1:children:rfc5646" />
</select>
<div class="language-search" data-preferred-languages="de,en,fr">
<input id="{xed:generate-id()}" type="hidden" class="language-search-input"/>
<!-- content is added by javascript -->
</div>
</xed:bind>
<xed:controls />
</div>
Expand Down
280 changes: 280 additions & 0 deletions ubo-common/src/main/resources/META-INF/resources/js/language-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* This file is part of *** M y C o R e ***
* See http://www.mycore.de/ for details.
*
* MyCoRe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MyCoRe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MyCoRe. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* @typedef {Object} ClassificationLabel
* @property {string} lang
* @property {string} text
* @property {string} description
*/

/**
* @typedef {Object} ClassificationCategory
* @property {string} ID
* @property {Array<ClassificationLabel>} labels
* @property {string} categories
*/

/**
* @typedef {Object} ClassificationResponse
* @property {string} ID
* @property {Array<ClassificationLabel>} labels
* @property {Array<ClassificationCategory>} categories
*/

/**
* Class to create a language search input. This input will show a list of languages to choose from. It expects the following
* structure:
* <div class="language-search" data-preferred-languages="de,en,fr">
* <input type="hidden" class="language-search-input"/>
* </div>
*/
class LanguageSearchInput {

/**
* @type {HTMLDataListElement}
*/
static dataList;
/**
* @type {Map<string, string>}
*/
static labelIdMap;

/**
* @type {Map<string, string>}
*/
static idLabelMap;
/**
* @type {HTMLInputElement}
*/
hiddenInput;
/**
* @type {HTMLDivElement}
*/
root;
/**
* @type {HTMLInputElement}
*/
searchInput;
/**
* @type {Array<string>}
*/
preferredLanguages;
/**
* Sometimes the labelIdMap and idLabelMap are not yet available. This promise will resolve once they are.
* @type {Promise<{
* labelIdMap: Map<string, string>,
* idLabelMap: Map<string, string>,
* }>}
*/
static mapPromise;

/**
* Creates a search input for languages.
* @param {HTMLDivElement} elem
*/
constructor(elem) {
this.root = elem;
this.hiddenInput = elem.querySelector('input[class="language-search-input"]');

if (elem.getAttribute("data-preferred-languages") != null) {
this.preferredLanguages = elem.getAttribute("data-preferred-languages").split(',');
} else {
this.preferredLanguages = [];
}

if (this.hiddenInput == null) {
console.error('No input found in language-search-input');
return;
}


this.initializeForm().then(() => {
console.log('Form initialized');
});

}

/**
* Finds the best matching label from the given array of labels.
* @param labelArray {Array<ClassificationLabel>} The array of labels to search in.
*/
static findBestMatchingLabel(labelArray) {
let bestMatch = labelArray[0];

labelArray.forEach(label => {
if (label.lang === window["currentLang"]) {
bestMatch = label;
}
});

return bestMatch;
}

/**
* Checks if the given search input is valid.
* @param searchInput {HTMLInputElement} The search input to check.
* @returns {boolean|boolean|*} True if the input is valid, false otherwise.
*/
static isValidInput(searchInput) {
return LanguageSearchInput.labelIdMap.has(searchInput.value);
}

/**
* Modifies the given search input to indicate if the input is valid or not.
* @param searchInput {HTMLInputElement} The search input to modify.
* @param isValid {boolean} True if the input is valid
*/
static setInputValidation(searchInput, isValid) {
if (isValid) {
searchInput.classList.remove('is-invalid');
} else {
searchInput.classList.add('is-invalid');
}
}

async initializeForm() {
const baseURL = window['webApplicationBaseURL'];
const currentLang = window["currentLang"];

if (!LanguageSearchInput.dataList) {
LanguageSearchInput.dataList = document.createElement('datalist');
LanguageSearchInput.dataList.id = 'language-search-list';

/**
* Used to queue up the resolvers for the mapPromise
* @type {Array<Function<{
* labelIdMap: Map<string, string>,
* idLabelMap: Map<string, string>,
* }>>}
*/
const resolverList = [];

LanguageSearchInput.mapPromise = new Promise(async (resolve, reject) => {
if(LanguageSearchInput.labelIdMap && LanguageSearchInput.idLabelMap){
resolve({
labelIdMap: LanguageSearchInput.labelIdMap,
idLabelMap: LanguageSearchInput.idLabelMap
});
return;
}
resolverList.push(resolve);
});

const baseURL = window['webApplicationBaseURL'];
const response = await fetch(baseURL + 'api/v2/classifications/rfc5646', {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});

/**
* @type {ClassificationResponse}
*/
const data = await response.json();


LanguageSearchInput.labelIdMap = /** @type {Map<string, string>} */ new Map();

LanguageSearchInput.idLabelMap = /** @type {Map<string, string>} */ new Map();
/**
* @type {Array<{el: HTMLOptionElement, category: ClassificationCategory}>}
*/
const prependLater = [];
data.categories.forEach(category => {
const optionElement = document.createElement("option");
const label = LanguageSearchInput.findBestMatchingLabel(category.labels).text;
optionElement.value = label;
optionElement.text = label;
LanguageSearchInput.labelIdMap.set(label, category.ID);
LanguageSearchInput.idLabelMap.set(category.ID, label);
const preferredLanguagePos = this.preferredLanguages.indexOf(category.ID);
if (preferredLanguagePos == -1) {
LanguageSearchInput.dataList.append(optionElement);
} else {
prependLater.push({el:optionElement , category:category});
}
});

resolverList.forEach((resolve) => {
resolve({
labelIdMap: LanguageSearchInput.labelIdMap,
idLabelMap: LanguageSearchInput.idLabelMap
});
});

prependLater.sort((a,b) => {
return this.preferredLanguages.indexOf(b.category.ID) - this.preferredLanguages.indexOf(a.category.ID);
}).forEach((el) => {
LanguageSearchInput.dataList.prepend(el.el);
});

this.root.append(LanguageSearchInput.dataList);
}

this.searchInput = document.createElement('input');
this.searchInput.type = 'text';
this.searchInput.classList.add('form-control');
this.searchInput.classList.add('language-search-input');
this.searchInput.setAttribute('list', 'language-search-list');
this.root.append(this.searchInput);

fetch(`${baseURL}rsc/locale/translate/${currentLang}/edit.language.placeholder`)
.then(response => response.text())
.then(translation => {
this.searchInput.placeholder = translation;
});


if(this.hiddenInput.value != null && this.hiddenInput.value.trim().length > 0) {
let idLabelMap = LanguageSearchInput.idLabelMap;
if(!LanguageSearchInput.idLabelMap){
const maps = await LanguageSearchInput.mapPromise;
idLabelMap = maps.idLabelMap;
}
if(idLabelMap.has(this.hiddenInput.value)) {
this.searchInput.value = idLabelMap.get(this.hiddenInput.value.trim());
}
}

this.searchInput.addEventListener('change', () => {
if (this.searchInput.value.trim().length === 0) {
this.hiddenInput.value = '';
LanguageSearchInput.setInputValidation(this.searchInput, true);
return;
}

if (LanguageSearchInput.isValidInput(this.searchInput)) {
this.hiddenInput.value = LanguageSearchInput.labelIdMap.get(this.searchInput.value);
LanguageSearchInput.setInputValidation(this.searchInput, true);
return;
}

LanguageSearchInput.setInputValidation(this.searchInput, false);
});
}
}

document.addEventListener('DOMContentLoaded', function () {

document.querySelectorAll('.language-search').forEach(function (input) {
new LanguageSearchInput(input);
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ edit.diss.referee = Bitte geben Sie auch den/die Betreuer*in bzw. D
edit.document = *Dokumententyp(en)
edit.keyword = Schlag-/Stichw\u00F6rter
edit.language = (*)Sprache
edit.language.placeholder = Suchen..
edit.legend.delete = Wenn Sie diesen Vorgang best\u00E4tigen, wird die Nutzerkennung gel\u00F6scht.
edit.legend.enter = Bitte geben Sie hier die Daten Ihrer Publikation ein.\r\n\u0009 Pflichtfelder sind mit einem <strong>(*)</strong> gekennzeichnet.\r\n\u0009 Durch klicken auf <strong>[+]</strong> k\u00F6nnen Sie ein Eingabefeld wiederholen!
edit.legend.enterDiss = Bitte geben Sie hier die Daten Ihrer Dissertation ein.\r\n\u0009 Pflichtfelder sind mit einem <strong>(*)</strong> gekennzeichnet.\r\n\u0009 Durch klicken auf <strong>[+]</strong> k\u00F6nnen Sie ein Eingabefeld wiederholen!
Expand Down
Loading

0 comments on commit 4347fa2

Please sign in to comment.