From b47ba7ea603305f6dff45d2cd0c2258d0d1432c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E5=BF=83?= <41134017+Lhcfl@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:41:02 +0800 Subject: [PATCH] DEV: Use Category's async method in param-input (#310) This commit refactors the category id normalization of param-input to use `Category.asyncFindBySlugPath`. Now, the category id input will use an async query when the default/initial value is category slug. Co-authored-by: Natalie <1555215+nattsw@users.noreply.github.com> --- .../discourse/components/param-input-form.gjs | 115 ++++++++++++------ .../param-input/category-id-input.gjs | 24 ++-- .../controllers/admin-plugins-explorer.js | 5 - .../controllers/group-reports-show.js | 7 -- .../components/param-input-test.js | 37 +++++- 5 files changed, 121 insertions(+), 67 deletions(-) diff --git a/assets/javascripts/discourse/components/param-input-form.gjs b/assets/javascripts/discourse/components/param-input-form.gjs index 1a58998..926cc3c 100644 --- a/assets/javascripts/discourse/components/param-input-form.gjs +++ b/assets/javascripts/discourse/components/param-input-form.gjs @@ -1,8 +1,9 @@ import Component from "@glimmer/component"; -import { action } from "@ember/object"; +import EmberObject, { action } from "@ember/object"; import { inject as service } from "@ember/service"; import { dasherize } from "@ember/string"; import { isEmpty } from "@ember/utils"; +import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; import Form from "discourse/components/form"; import Category from "discourse/models/category"; import I18n from "I18n"; @@ -49,22 +50,20 @@ const ERRORS = { function digitalizeCategoryId(value) { value = String(value || ""); const isPositiveInt = /^\d+$/.test(value); - if (!isPositiveInt) { - if (/\//.test(value)) { - const match = /(.*)\/(.*)/.exec(value); - if (!match) { - value = null; - } else { - value = Category.findBySlug( - dasherize(match[2]), - dasherize(match[1]) - )?.id; - } - } else { - value = Category.findBySlug(dasherize(value))?.id; - } + if (!isPositiveInt && value.trim()) { + return Category.asyncFindBySlugPath(dasherize(value)) + .then((res) => res.id) + .catch((err) => { + if (err.jqXHR?.status === 404) { + throw new ParamValidationError( + `${ERRORS.NO_SUCH_CATEGORY}: ${value}` + ); + } else { + throw new Error(err.errorThrow || err.message); + } + }); } - return value?.toString(); + return value; } function normalizeValue(info, value) { @@ -158,36 +157,72 @@ export default class ParamInputForm extends Component { infoOf = {}; form = null; + promiseNormalizations = []; + constructor() { super(...arguments); - - const initialValues = this.args.initialValues; - for (const info of this.args.paramInfo) { - const identifier = info.identifier; - - // access parsed params if present to update values to previously ran values - let initialValue; - if (initialValues && identifier in initialValues) { - initialValue = initialValues[identifier]; - } else { - // if no parsed params then get and set default values - initialValue = info.default; - } - this.data[identifier] = normalizeValue(info, initialValue); - this.paramInfo.push({ - ...info, - validation: validationOf(info), - validate: this.validatorOf(info), - component: componentOf(info), - }); - this.infoOf[identifier] = info; - } + this.initializeParams(); this.args.onRegisterApi?.({ submit: this.submit, + allNormalized: Promise.allSettled(this.promiseNormalizations), }); } + initializeParams() { + this.args.paramInfo.forEach((info) => { + const identifier = info.identifier; + const pinfo = this.createParamInfo(info); + + this.paramInfo.push(pinfo); + this.infoOf[identifier] = info; + + const normalized = this.getNormalizedValue(info); + + if (normalized instanceof Promise) { + this.handlePromiseNormalization(normalized, pinfo); + } else { + this.data[identifier] = normalized; + } + }); + } + + createParamInfo(info) { + return EmberObject.create({ + ...info, + validation: validationOf(info), + validate: this.validatorOf(info), + component: componentOf(info), + }); + } + + getNormalizedValue(info) { + const initialValues = this.args.initialValues; + const identifier = info.identifier; + return normalizeValue( + info, + initialValues && identifier in initialValues + ? initialValues[identifier] + : info.default + ); + } + + handlePromiseNormalization(promise, pinfo) { + this.promiseNormalizations.push(promise); + pinfo.set("loading", true); + this.data[pinfo.identifier] = null; + + promise + .then((res) => this.form.set(pinfo.identifier, res)) + .catch((err) => + this.form.addError(pinfo.identifier, { + title: pinfo.identifier, + message: err.message, + }) + ) + .finally(() => pinfo.set("loading", false)); + } + getErrorFn(info) { const isPositiveInt = (value) => /^\d+$/.test(value); const VALIDATORS = { @@ -321,6 +356,10 @@ export default class ParamInputForm extends Component { as |field| > + {{/each}} diff --git a/assets/javascripts/discourse/components/param-input/category-id-input.gjs b/assets/javascripts/discourse/components/param-input/category-id-input.gjs index 2222eb4..96d5baa 100644 --- a/assets/javascripts/discourse/components/param-input/category-id-input.gjs +++ b/assets/javascripts/discourse/components/param-input/category-id-input.gjs @@ -1,26 +1,20 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { action } from "@ember/object"; import CategoryChooser from "select-kit/components/category-chooser"; -export default class GroupListInput extends Component { - @tracked value; - constructor() { - super(...arguments); - this.value = this.args.field.value; - } - - @action - update(id) { - this.value = id; - this.args.field.set(id); +export default class CategoryIdInput extends Component { + // CategoryChooser will try to modify the value of value, + // triggering a setting-on-hash error. So we have to do the dirty work. + get data() { + return { + value: this.args.field.value, + }; }