[IMP] web: model field selector: search on string and name in debug mode

We allow the user to make a fuzzy search on the string and the name of
the fields when the props isDebugMode is true.

Part-of: odoo/odoo#121688
This commit is contained in:
Mathieu Duckerts-Antoine
2023-07-13 14:47:11 +02:00
parent a3b00f2dca
commit a1365d7655
3 changed files with 76 additions and 13 deletions
@@ -8,11 +8,13 @@ import { sortBy } from "@web/core/utils/arrays";
import { useService } from "@web/core/utils/hooks";
class Page {
constructor(resModel, fieldDefs, previousPage = null, selectedName = null) {
constructor(resModel, fieldDefs, options = {}) {
this.resModel = resModel;
this.fieldDefs = fieldDefs;
const { previousPage = null, selectedName = null, isDebugMode } = options;
this.previousPage = previousPage;
this.selectedName = selectedName;
this.isDebugMode = isDebugMode;
this.sortedFieldNames = sortBy(Object.keys(fieldDefs), (key) => fieldDefs[key].string);
this.fieldNames = this.sortedFieldNames;
this.query = "";
@@ -74,11 +76,13 @@ class Page {
this.query = query;
this.fieldNames = this.sortedFieldNames;
if (query) {
this.fieldNames = fuzzyLookup(
query,
this.fieldNames,
(key) => this.fieldDefs[key].string
);
this.fieldNames = fuzzyLookup(query, this.fieldNames, (key) => {
const vals = [this.fieldDefs[key].string];
if (this.isDebugMode) {
vals.push(key);
}
return vals;
});
}
this.resetFocusedFieldName();
}
@@ -149,7 +153,10 @@ export class ModelFieldSelectorPopover extends Component {
this.state.page.selectedName = fieldDef.name;
const { resModel, fieldDefs } = modelsInfo.at(-1);
this.openPage(
new Page(resModel, this.filter(fieldDefs, this.state.page.path), this.state.page)
new Page(resModel, this.filter(fieldDefs, this.state.page.path), {
previousPage: this.state.page,
isDebugMode: this.props.isDebugMode,
})
);
}
@@ -166,7 +173,9 @@ export class ModelFieldSelectorPopover extends Component {
async loadPages(resModel, path) {
if (typeof path !== "string" || !path.length) {
const fieldDefs = await this.fieldService.loadFields(resModel);
return new Page(resModel, this.filter(fieldDefs, path));
return new Page(resModel, this.filter(fieldDefs, path), {
isDebugMode: this.props.isDebugMode,
});
}
const { isInvalid, modelsInfo, names } = await this.fieldService.loadPath(resModel, path);
switch (isInvalid) {
@@ -174,14 +183,21 @@ export class ModelFieldSelectorPopover extends Component {
throw new Error(`Invalid model name: ${resModel}`);
case "path": {
const { resModel, fieldDefs } = modelsInfo[0];
return new Page(resModel, this.filter(fieldDefs, path), null, path);
return new Page(resModel, this.filter(fieldDefs, path), {
selectedName: path,
isDebugMode: this.props.isDebugMode,
});
}
default: {
let page = null;
for (let index = 0; index < names.length; index++) {
const name = names[index];
const { resModel, fieldDefs } = modelsInfo[index];
page = new Page(resModel, this.filter(fieldDefs, path), page, name);
page = new Page(resModel, this.filter(fieldDefs, path), {
previousPage: page,
selectedName: name,
isDebugMode: this.props.isDebugMode,
});
}
return page;
}
+19 -3
View File
@@ -2,6 +2,22 @@
import { unaccent } from "./strings";
/**
* @param {string} pattern
* @param {string|string[]} strs
* @returns {number}
*/
function match(pattern, strs) {
if (!Array.isArray(strs)) {
strs = [strs];
}
let globalScore = 0;
for (const str of strs) {
globalScore = Math.max(globalScore, _match(pattern, str));
}
return globalScore;
}
/**
* This private function computes a score that represent the fact that the
* string contains the pattern, or not
@@ -17,7 +33,7 @@ import { unaccent } from "./strings";
* @param {string} str
* @returns {number}
*/
function match(pattern, str) {
function _match(pattern, str) {
let totalScore = 0;
let currentScore = 0;
const len = str.length;
@@ -47,7 +63,7 @@ function match(pattern, str) {
* @template T
* @param {string} pattern
* @param {T[]} list
* @param {(element: T) => string} fn
* @param {(element: T) => (string|string[])} fn
* @returns {T[]}
*/
export function fuzzyLookup(pattern, list, fn) {
@@ -72,5 +88,5 @@ export function fuzzyLookup(pattern, list, fn) {
* @returns {boolean}
*/
export function fuzzyTest(pattern, string) {
return match(pattern, string) !== 0;
return _match(pattern, string) !== 0;
}
@@ -831,4 +831,35 @@ QUnit.module("Components", (hooks) => {
);
assert.containsNone(target, ".o_model_field_selector_warning");
});
QUnit.test("search on field string and name in debug mode", async (assert) => {
patchWithCleanup(browser, { setTimeout: (fn) => fn() }); // for debouncedSearchFields
serverData.models.partner.fields.ucit = {
type: "char",
string: "Some string",
searchable: true,
};
class Parent extends Component {
static components = { ModelFieldSelector };
static template = xml`
<ModelFieldSelector
readonly="false"
resModel="'partner'"
path="'foo'"
isDebugMode="true"
/>
`;
}
await mountComponent(Parent);
await openModelFieldSelectorPopover(target);
await editInput(
target,
".o_model_field_selector_popover .o_model_field_selector_popover_search input",
"uct"
);
assert.deepEqual(getDisplayedFieldNames(target), [
"Productproduct_id (many2one)",
"Some stringucit (char)",
]);
});
});