Skip to content
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

Revert select value type support #277

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

eyelidlessness
Copy link
Member

@eyelidlessness eyelidlessness commented Jan 16, 2025

I have verified this PR works in these browsers (latest versions):

  • Chrome
  • Firefox
  • Safari (macOS)
  • Safari (iOS)

What else has been done to verify that this works as intended?

Since this rolls back functionality in #273, the tests added for that functionality have been removed. I think everything else is covered by existing tests and/or type checks.

Why is this the best possible solution? Were any other approaches considered?

This leaves most of the other substantive improvements from #273 intact. Any considerations there apply here.

How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?

This is technically a regression! As of #273, Web Forms behavior for <select1> with a non-string <bind type> was consistent with Collect/JavaRosa (for the current set of supported bind/value types). But we've decided that is an unsupported use case for now, as it isn't possible to produce an affected form with XLSForm/pyxform.

Do we need any specific form for testing your changes? If so, please attach one.

The fixture added in #273 now produces an error on load (which is now expected). The fixture added in this commit demonstrates correct support for spaces in <select1> values (which could have been added in that PR as well).

What's changed

The primary purpose of this change is to revert support for non-string <bind type>s associated with a <select> or <select1> control (introduced in #273). It also establishes the same precedent for <odk:rank>. The bulk of this aspect of the change involves:

  • Eliminating any ValueType type parameterization associated with SelectNode and its implementation
  • Eliminating the vast majority of implementation code supporting that type parameterization

Some of the breadcrumbs for that functionality are left intact, in ValueArrayCodec. There are other use cases where we'll benefit from being able to derive encoding/decoding of array items (e.g. "select from map", which was part of the motivation for typed select support in the first place).

I also took this PR as an opportunity to address a gap in the preparation for <odk:rank>, which I had neglected to address in #273. Specifically, the PR's first commit makes some structural changes to prepare for parsing <odk:rank><item> or <odk:rank><itemset>.

Lastly, since I was already looking in that area, I went ahead and took care of these maintenance chores:

  • The SelectDefinition class associated with the body element is now named SelectControlDefinition, consistent with other parsed representations of similar controls. This eliminates a naming conflict where we also have an interface named SelectDefinition representing the NodeDefinition associated with that control. (We really should take a more holistic look at naming, especially in these earlier areas of the engine package. For now, this is a piecemeal change aiming for consistency with current thinking.)
  • ItemsetNodesetContext, which was unused, is removed.

This is an engine-internal reorganization to improve some less-than-ideal naming and code structure around parsing selects, and helping to prepare for support of `<odk:rank>`.

- Addresses a naming conflict between `SelectDefinition` (as a subtype of `NodeDefinition`) and `SelectDefinition` (as a parsed representation of `<select>` and `<select1>` **body elements**). The parsed body element is now named `SelectControlDefinition`. This naming is still not ideal! But it is consistent with the conceptual equivalents for other controls.
- Anticipates similar parsing for `<odk:rank>` body elements. This may be easy to miss in review without calling it out, as it’s a change in file system structure. Specifically, each of the following are no longer structured as select-specific:
    - `ItemDefinition`
    - `ItemsetDefinition`

    This helps to prepare for `<odk:rank>` because it’s expected that both will be associated with a `RankControlDefinition`, providing the same parsed information about the control’s items to an implementation of `RankNode`/`RankControl`.
- Removes `ItemsetNodesetContext`, which is evidently not in use at all!
Support for non-string bind/value types was introduced in #273. That support exceeds the current ODK XForms spec, which specifies both controls as supporting only a string type.

We initially decided to merge that expansion on these bases:

- XLSForms/pyxform do not produce forms with non-string select types, so it would only occur for forms authored as XML XForms, and would presumably be an intentional form design decision

- The behavior (tested with `<select1>` + `<bind type=“int”>`) is consistent with Collect/JavaRosa

We have since decided to revert that for now, because it would violate assumptions about _multiple values_ (from a `<select>`, and soon from `<odk:rank>`) associated with a non-string `<bind type>`. For example, this would break downstream projects which derive database schema types (e.g. SQL column types) from a form definition’s `<bind type>`.
Copy link

changeset-bot bot commented Jan 16, 2025

🦋 Changeset detected

Latest commit: 0a6010e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@getodk/common Patch
@getodk/scenario Patch
@getodk/web-forms Minor
@getodk/xpath Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@eyelidlessness eyelidlessness marked this pull request as ready for review January 16, 2025 21:17
Copy link
Member

@lognaturel lognaturel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like what I expect from a pretty quick pass through!

@@ -48,7 +48,7 @@ const BodyElementDefinitionConstructors = [
PresentationGroupDefinition,
StructuralGroupDefinition,
InputControlDefinition,
SelectDefinition,
SelectControlDefinition,
Copy link
Member

@lognaturel lognaturel Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this will expand to something like ItemBasedControlDefinition with the addition of rank?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that becoming an abstract base class for select/select1/rank. Both concrete classes would still be enumerated here though, as it's used for instantiation during parsing. And the base class would make even more sense when we split select/select1.

Copy link
Collaborator

@latin-panda latin-panda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sending this so fast!

I made a couple of suggestions, but it's mainly questions.

Let me know what you think.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions:
The documentation of the scenario package says:

This package implements a client of @getodk/xforms-engine, and a suite of tests and benchmarks against that engine.

  1. When it says ... implements a client..., is it a client to perform tests based on JavaRosa? Asking because when looking at the xforms-engine, there is already a client directory, and I was wondering about the difference.

  2. ... and benchmarks against that engine., is it referring to performance tests? And, is there a baseline performance for what "good" means?
    If I recall correctly, there was a delay when having groups and repeats simultaneously, which we'd like not to make it slower in Web Forms.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:
I understand the intent behind prioritizing reusability, but I'm considering ways to reduce the spread of what defines "select" to make it more self-contained as a component. Additionally, I suggest inverting the responsibility for the ID format.

Assuming the ID format is tied to the select functionality in the engine, what about moving the selectOptionId(...) logic into the Select1Component? Then, you could pass the formatter as an Input to the RadioButton and CheckboxWidget components.

This approach shifts the responsibility for defining the ID to the component that uses RadioButton and CheckboxWidget, which would provide greater flexibility. For example, if these components are later used in contexts requiring a different ID format, the adjustment would be simpler and more modular.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
Is the hierarchy defined based on the XML output?

For instance, are group and repeat considered higher-order components, while select functions as a child or leaf in the tree? Additionally, should all new components be declared in this file first to ensure they are correctly mapped in the typing?

It seems like a good place to start for a new contributor to understanding the engine.

const { valueType } = definition;

super(
`Selects of type ${valueType} are not currently supported. If this functionality would be useful for your form, your feedback is welcome!`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion:

This seems like a common type of error for other components. What do you think about using generics in the declaration? Then, in the select definition (SelectNode? or SelectControl?), we could define the error type, extend it, and pass the types through the generic.

By centralizing the possible errors for select within select definition (SelectNode? or SelectControl?), it would make them easier to discover and help provide a clearer picture of the entire select functionality.

@@ -0,0 +1 @@
export abstract class XFormsSpecViolationError extends Error {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

I like reusable errors in this folder!
What is this class for? What will it do?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

  1. When codec run? I'm looking to know how to verify the output is what we expect
  2. I wonder if the Rank codec will be the same as this one and if we can reuse or duplicate it.
    2.1 To reduce dependency between components, I would make 1 independent codec for Rank, but I want to know based on your experience with xforms

@@ -3,7 +3,7 @@ import type {
KnownAttributeLocalNamedElement,
LocalNamedElement,
} from '@getodk/common/types/dom.ts';
import type { SelectElement } from '../../parse/body/control/select/SelectDefinition';
import type { SelectElement } from '../../parse/body/control/SelectControlDefinition.ts';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

Why do we have one for Select, while not for Range, Note, Inputs?
Looking to understand the reason so I can see if I need it for Rank or Geopoint

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions:

I didn't find Range in the reactivity directory; maybe I missed it.

Why don't we have it but have it for Select?

What is the responsibility of reactivity?

@@ -13,7 +14,7 @@ export class ItemDefinition extends BodyElementDefinition<'item'> {

constructor(
form: XFormDefinition,
override readonly parent: AnySelectDefinition,
override readonly parent: AnySelectControlDefinition,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

I'm supposing that rank will use this, too. Does it make sense?

override readonly parent: AnySelectControlDefinition | RankControlDefinition,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:

Does body parsing happen after the engine's client? I mean, the entry point is client then body parsing runs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants