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>
This commit is contained in:
锦心 2024-08-21 15:41:02 +08:00 committed by GitHub
parent 1d991c6192
commit b47ba7ea60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 121 additions and 67 deletions

View File

@ -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|
>
<info.component @field={{field}} @info={{info}} />
<ConditionalLoadingSpinner
@condition={{info.loading}}
@size="small"
/>
</form.Field>
</div>
{{/each}}

View File

@ -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,
};
}
<template>
<@field.Custom id={{@field.id}}>
<CategoryChooser
@value={{this.value}}
@onChange={{this.update}}
@value={{this.data.value}}
@onChange={{@field.set}}
name={{@info.identifier}}
/>
</@field.Custom>

View File

@ -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;

View File

@ -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;

View File

@ -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`
<ParamInputForm
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await this.paramInputApi.allNormalized;
this.paramInputApi.submit().then((res) => {
assert.strictEqual(res.category_id, "1003");
});
});
});