New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug 1867824: Fix dynamic form field ordering logic #6329
Conversation
Update form field ordering logic so that behavior matches documentation. Falls back to ordering by descriptor order. Move some common dynamic form type definitions into types.ts to prevent dependency cycles.
@TheRealJon: This pull request references Bugzilla bug 1867824, which is valid. The bug has been moved to the POST state. The bug has been updated to refer to the pull request using the external bug tracker. 3 validation(s) were run on this bug
In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
@TheRealJon: This pull request references Bugzilla bug 1867824, which is valid. 3 validation(s) were run on this bug
In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/assign @spadgett |
|
||
export type DynamicFormFieldOptionsList = { | ||
label: string; | ||
value: string; | ||
}[]; | ||
|
||
export type DynamicFormFieldDependency = { | ||
controlFieldPath: string; | ||
controlFieldValue: string; | ||
controlFieldName: string; | ||
}; | ||
|
||
export type UiSchemaOptionsWithDependency = { | ||
dependency?: DynamicFormFieldDependency; | ||
}; | ||
|
||
export type DynamicFormSchemaError = { | ||
title: string; | ||
message: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was having issues with dependency cycles while trying to improve the types for the sort order function in utils, so I moved these reusable types here to prevent future issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/lgtm
Thanks @TheRealJon
controlFieldName: string; | ||
}; | ||
|
||
export type UiSchemaOptionsWithDependency = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: console naming conventions would make this capital UI
export type UiSchemaOptionsWithDependency = { | |
export type UISchemaOptionsWithDependency = { |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: spadgett, TheRealJon The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
const { dependency } = getUiOptions(uiSchema ?? {}) as DependencyUIOption; // Type defs for this function are awful | ||
const { dependency } = getUiOptions(uiSchema ?? {}) as UiSchemaOptionsWithDependency; // Type defs for this function are awful | ||
if (dependency) { | ||
setDependencyMet( | ||
dependency.value === | ||
_.get(formContext.formData ?? {}, ['spec', ...(dependency.path ?? [])], '').toString(), | ||
dependency?.controlFieldValue === | ||
_.get( | ||
formContext.formData ?? {}, | ||
['spec', ...(dependency?.controlFieldPath ?? [])], | ||
'', | ||
).toString(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the uiSchema dependency option to include the control field name (the end of the path) to make sorting easier, and updated property naming to be more descriptive while I was at it.
@@ -1,4 +1,4 @@ | |||
export enum SchemaType { | |||
export enum JSONSchemaType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More descriptive name since 'schema' could mean UI schema or JSON schema in the context of the DynamicForm component.
/** | ||
* Give a property name a sort wieght based on whether it has ui schema, is required, or is a | ||
* control field for a property with a field dependency. A lower weight means higher sort order. | ||
* Fields are weighted according to the following tiers: | ||
* Tier 1 (negative 10^9 - 10^6 magnitude): Required fields with ui schema | ||
* Tier 2 (negative 10^9 magnitude): Required fields without ui schema | ||
* Tier 3 (negative 10^6 magnitude): Optional fields with ui schema | ||
* Tier 4 (positive 10^3 maginitude): Control fields that don't fit any above | ||
* Tier 5 (Infinity): All other fields | ||
* | ||
* Within each of the above tiers, fields are further weighted based on field dependency and ui | ||
* schema defined sort order: | ||
* - Fields without dependency - base weight + ui schema sort order | ||
* - Control field - base weight + ui schema sort order + (nth control field) * 10000 | ||
* - Dependent field - control field weight + ui schema sort order + 1 | ||
* | ||
* These weight numbers are arbitrary, but spaced far enough apart to prevent collisions. | ||
*/ | ||
const getJSONSchemaPropertySortWeight = ( | ||
property: string, | ||
jsonSchema: JSONSchema6, | ||
uiSchema: UiSchema, | ||
): number => { | ||
const isRequired = (jsonSchema?.required ?? []).includes(property); | ||
const propertyUISchema = uiSchema?.[property]; | ||
|
||
// Any sibling has a dependency with this as the control field. | ||
const isControlField = _.some( | ||
uiSchema, | ||
({ 'ui:dependency': dependency }) => dependency?.controlFieldName === property, | ||
); | ||
|
||
// Property sort order from ui schema (adjusted to 1-based origin). Use propertyNames.length | ||
// when no uiSchema sortOrder exists. Ensures properties without uiSchema sort order have a | ||
// higher weight than those with. | ||
const propertySortOrder = Number( | ||
propertyUISchema?.['ui:sortOrder'] ?? _.keys(jsonSchema?.properties).length, | ||
); | ||
|
||
// This property's control field name, if it exists | ||
const controlFieldName = propertyUISchema?.['ui:dependency']?.controlFieldName; | ||
|
||
// A small offset that is added to the base weight so that control fields get sorted | ||
// below other fields in the same 'tier', and allows for depenendt fields to be sorted | ||
// directly after their control field. | ||
const controlFieldOffset = isControlField ? propertySortOrder * THOUSAND : 0; | ||
|
||
// Total offset to be added to base tier | ||
const offset = controlFieldOffset + propertySortOrder; | ||
|
||
// If this property is a dependent, it's weight is based on it's control field | ||
if (controlFieldName) { | ||
return getJSONSchemaPropertySortWeight(controlFieldName, jsonSchema, uiSchema) + offset + 1; | ||
} | ||
|
||
// Tier 1 = -1001000000 (negative one billion one million) + offset | ||
// Tier 2 = -1000000000 (negagive one billion) + offset | ||
// Tier 3 = -1000000 (negative one million) + offset | ||
// Tier 4 = 0 + offset | ||
// Tier 5 = Infinity | ||
return ( | ||
// Doesn't meet any sorting criteria, set to infinity | ||
(!isRequired && !propertyUISchema && !controlFieldOffset ? Infinity : 0) - | ||
(isRequired ? BILLION : 0) - | ||
(propertyUISchema ? MILLION : 0) + | ||
offset | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pulled this function out of getJSONSchemaOrder
scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to improve the comments and logic to make this a little bit easier to understand.
// Property sort order from ui schema (adjusted to 1-based origin). Use propertyNames.length | ||
// when no uiSchema sortOrder exists. Ensures properties without uiSchema sort order have a | ||
// higher weight than those with. | ||
const propertySortOrder = Number( | ||
propertyUISchema?.['ui:sortOrder'] ?? _.keys(jsonSchema?.properties).length, | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main bug fix. I added logic to include the original descriptor index as a sort weight modifier.
const [, path, value] = capability.match(REGEXP_FIELD_DEPENDENCY_PATH_VALUE) ?? []; | ||
if (!!path && !!value) { | ||
return { 'ui:dependency': { path: descriptorPathToUISchemaPath(path), value } }; | ||
} | ||
return {}; | ||
const [, path, controlFieldValue] = capability.match(REGEXP_FIELD_DEPENDENCY_PATH_VALUE) ?? []; | ||
const controlFieldPath = descriptorPathToUISchemaPath(path); | ||
const controlFieldName = _.last(controlFieldPath); | ||
return { | ||
...(path && | ||
controlFieldValue && { | ||
'ui:dependency': { | ||
controlFieldPath, | ||
controlFieldValue, | ||
controlFieldName, | ||
}, | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update property names to be more descriptive and add control field name for easier reference when sorting dependent/control fields.
@@ -166,6 +175,7 @@ export const descriptorsToUISchema = ( | |||
...(description && { 'ui:description': description }), | |||
...(displayName && { 'ui:title': displayName }), | |||
...capabilitiesUISchema, | |||
'ui:sortOrder': index + 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add descriptor index to uiSchema to be used in sorting logic.
/retest Please review the full test history for this PR and help us cut down flakes. |
4 similar comments
/retest Please review the full test history for this PR and help us cut down flakes. |
/retest Please review the full test history for this PR and help us cut down flakes. |
/retest Please review the full test history for this PR and help us cut down flakes. |
/retest Please review the full test history for this PR and help us cut down flakes. |
@TheRealJon: All pull requests linked via external trackers have merged: openshift/console#6329. Bugzilla bug 1867824 has been moved to the MODIFIED state. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Update form field ordering logic so that behavior matches documentation. Falls back to ordering by descriptor order.
Move some common dynamic form type definitions into types.ts to prevent dependency cycles.
cc @tlwu2013