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 Component from "@glimmer/component";
import { action } from "@ember/object"; import EmberObject, { action } from "@ember/object";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import Form from "discourse/components/form"; import Form from "discourse/components/form";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import I18n from "I18n"; import I18n from "I18n";
@ -49,22 +50,20 @@ const ERRORS = {
function digitalizeCategoryId(value) { function digitalizeCategoryId(value) {
value = String(value || ""); value = String(value || "");
const isPositiveInt = /^\d+$/.test(value); const isPositiveInt = /^\d+$/.test(value);
if (!isPositiveInt) { if (!isPositiveInt && value.trim()) {
if (/\//.test(value)) { return Category.asyncFindBySlugPath(dasherize(value))
const match = /(.*)\/(.*)/.exec(value); .then((res) => res.id)
if (!match) { .catch((err) => {
value = null; if (err.jqXHR?.status === 404) {
} else { throw new ParamValidationError(
value = Category.findBySlug( `${ERRORS.NO_SUCH_CATEGORY}: ${value}`
dasherize(match[2]), );
dasherize(match[1]) } else {
)?.id; throw new Error(err.errorThrow || err.message);
} }
} else { });
value = Category.findBySlug(dasherize(value))?.id;
}
} }
return value?.toString(); return value;
} }
function normalizeValue(info, value) { function normalizeValue(info, value) {
@ -158,36 +157,72 @@ export default class ParamInputForm extends Component {
infoOf = {}; infoOf = {};
form = null; form = null;
promiseNormalizations = [];
constructor() { constructor() {
super(...arguments); super(...arguments);
this.initializeParams();
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.args.onRegisterApi?.({ this.args.onRegisterApi?.({
submit: this.submit, 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) { getErrorFn(info) {
const isPositiveInt = (value) => /^\d+$/.test(value); const isPositiveInt = (value) => /^\d+$/.test(value);
const VALIDATORS = { const VALIDATORS = {
@ -321,6 +356,10 @@ export default class ParamInputForm extends Component {
as |field| as |field|
> >
<info.component @field={{field}} @info={{info}} /> <info.component @field={{field}} @info={{info}} />
<ConditionalLoadingSpinner
@condition={{info.loading}}
@size="small"
/>
</form.Field> </form.Field>
</div> </div>
{{/each}} {{/each}}

View File

@ -1,26 +1,20 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import CategoryChooser from "select-kit/components/category-chooser"; import CategoryChooser from "select-kit/components/category-chooser";
export default class GroupListInput extends Component { export default class CategoryIdInput extends Component {
@tracked value; // CategoryChooser will try to modify the value of value,
constructor() { // triggering a setting-on-hash error. So we have to do the dirty work.
super(...arguments); get data() {
this.value = this.args.field.value; return {
} value: this.args.field.value,
};
@action
update(id) {
this.value = id;
this.args.field.set(id);
} }
<template> <template>
<@field.Custom id={{@field.id}}> <@field.Custom id={{@field.id}}>
<CategoryChooser <CategoryChooser
@value={{this.value}} @value={{this.data.value}}
@onChange={{this.update}} @onChange={{@field.set}}
name={{@info.identifier}} name={{@info.identifier}}
/> />
</@field.Custom> </@field.Custom>

View File

@ -359,11 +359,6 @@ export default class PluginsExplorerController extends Controller {
this.form = form; this.form = form;
} }
@action
updateParams(identifier, value) {
this.selectedItem.set(`params.${identifier}`, value);
}
@action @action
updateSearch(value) { updateSearch(value) {
this.search = 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 @action
onRegisterApi(form) { onRegisterApi(form) {
this.form = form; this.form = form;

View File

@ -60,7 +60,7 @@ const InputTestCases = [
tests: [ tests: [
{ {
input: null, input: null,
data_null: undefined, data_null: "",
error: ERRORS.REQUIRED, error: ERRORS.REQUIRED,
}, },
{ {
@ -111,8 +111,9 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
initialValues: config.initial initialValues: config.initial
? { [testcase.type]: config.initial } ? { [testcase.type]: config.initial }
: {}, : {},
onRegisterApi: ({ submit }) => { onRegisterApi: ({ submit, allNormalized }) => {
this.submit = submit; this.submit = submit;
this.allNormalized = allNormalized;
}, },
}); });
@ -124,6 +125,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
@onRegisterApi={{this.onRegisterApi}} @onRegisterApi={{this.onRegisterApi}}
/>`); />`);
await this.allNormalized;
if (config.initial || config.default) { if (config.initial || config.default) {
const data = await this.submit(); const data = await this.submit();
const val = config.initial || config.default; const val = config.initial || config.default;
@ -201,4 +204,34 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
await fillIn(`[name="string"]`, ""); await fillIn(`[name="string"]`, "");
assert.rejects(this.submit()); 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");
});
});
}); });