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,
+ };
}
<@field.Custom id={{@field.id}}>
@field.Custom>
diff --git a/assets/javascripts/discourse/controllers/admin-plugins-explorer.js b/assets/javascripts/discourse/controllers/admin-plugins-explorer.js
index 8b14341..dfe49cb 100644
--- a/assets/javascripts/discourse/controllers/admin-plugins-explorer.js
+++ b/assets/javascripts/discourse/controllers/admin-plugins-explorer.js
@@ -359,11 +359,6 @@ export default class PluginsExplorerController extends Controller {
this.form = form;
}
- @action
- updateParams(identifier, value) {
- this.selectedItem.set(`params.${identifier}`, value);
- }
-
@action
updateSearch(value) {
this.search = value;
diff --git a/assets/javascripts/discourse/controllers/group-reports-show.js b/assets/javascripts/discourse/controllers/group-reports-show.js
index d429dd3..41f0b8b 100644
--- a/assets/javascripts/discourse/controllers/group-reports-show.js
+++ b/assets/javascripts/discourse/controllers/group-reports-show.js
@@ -130,13 +130,6 @@ export default class GroupReportsShowController extends Controller {
});
}
- // This is necessary with glimmer's one way data stream to get the child's
- // changes of 'params' to bubble up.
- @action
- updateParams(identifier, value) {
- this.set(`model.params.${identifier}`, value);
- }
-
@action
onRegisterApi(form) {
this.form = form;
diff --git a/test/javascripts/components/param-input-test.js b/test/javascripts/components/param-input-test.js
index a116e28..b9e9e10 100644
--- a/test/javascripts/components/param-input-test.js
+++ b/test/javascripts/components/param-input-test.js
@@ -60,7 +60,7 @@ const InputTestCases = [
tests: [
{
input: null,
- data_null: undefined,
+ data_null: "",
error: ERRORS.REQUIRED,
},
{
@@ -111,8 +111,9 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
initialValues: config.initial
? { [testcase.type]: config.initial }
: {},
- onRegisterApi: ({ submit }) => {
+ onRegisterApi: ({ submit, allNormalized }) => {
this.submit = submit;
+ this.allNormalized = allNormalized;
},
});
@@ -124,6 +125,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
@onRegisterApi={{this.onRegisterApi}}
/>`);
+ await this.allNormalized;
+
if (config.initial || config.default) {
const data = await this.submit();
const val = config.initial || config.default;
@@ -201,4 +204,34 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
await fillIn(`[name="string"]`, "");
assert.rejects(this.submit());
});
+
+ test("async normalizion", async function (assert) {
+ this.setProperties({
+ param_info: [
+ {
+ identifier: "category_id",
+ type: "category_id",
+ default: "support",
+ nullable: false,
+ },
+ ],
+ initialValues: {},
+ onRegisterApi: (paramInputApi) => {
+ this.paramInputApi = paramInputApi;
+ },
+ });
+
+ await render(hbs`
+ `);
+
+ await this.paramInputApi.allNormalized;
+
+ this.paramInputApi.submit().then((res) => {
+ assert.strictEqual(res.category_id, "1003");
+ });
+ });
});