feat: support full user targeting on workflow USER TASK#171
feat: support full user targeting on workflow USER TASK#171engalar wants to merge 4 commits intomendixlabs:mainfrom
Conversation
ako
left a comment
There was a problem hiding this comment.
Clean fix with thoughtful backward compatibility.
What's good
Backward compatibility done right. Parser reads both legacy UserSource and current UserTargeting fields, mapping old and new $Type variants to the same SDK types — projects from any Mendix version round-trip.
Grammar extension is non-breaking. TARGETING (USERS | GROUPS)? keeps existing scripts working.
Full pipeline coverage across all 7 layers (SDK, parser, writer, grammar, AST, visitor, executor, formatter).
Good test coverage — 6 parser tests covering all targeting variants including the legacy field.
Concerns
XPathConstraint fallback may be reversed. Parser checks XPath first, then falls back to XPathConstraint, but the writer always emits XPathConstraint. If real Mendix BSON uses XPathConstraint, the XPath check is dead code; if both exist in different versions, the priority should match what's actually used.
Group source types may be missing fields. MicroflowGroupSource only stores Microflow. If group targeting has additional fields (e.g., context variable like user targeting may have), they'd be silently lost. Worth verifying against a real Studio Pro export.
MULTI USER TASK grammar duplication. Identical targeting blocks in both USER TASK and MULTI USER TASK rules — could be factored into a userTargetingClause rule. Not blocking.
Visitor's group detection is coarse. len(utCtx.AllGROUPS()) > 0 flags both microflow and xpath as group if either uses GROUPS. Probably impossible in practice (only one targeting clause allowed) but worth a guard.
No DESCRIBE roundtrip test. Tests cover parsing only. A test that creates → describes → re-parses would catch formatter mismatches.
LGTM — fix is correct, concerns are minor improvements.
b25df6d to
e4ca07b
Compare
AI Code ReviewWhat Looks Good
RecommendationThe PR appears to be well-implemented and follows all the necessary guidelines from CLAUDE.md. It addresses the full stack consistently, maintains backward compatibility, follows MDL design principles, and includes appropriate test coverage as claimed in the PR body. Approve this pull request. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
e4ca07b to
5386fc4
Compare
ako
left a comment
There was a problem hiding this comment.
PR Review: feat: support full user targeting on workflow USER TASK
Overall: Clean, well-scoped PR that adds group targeting support for workflow user tasks. The implementation follows existing patterns correctly and the test coverage is solid. A few items to address:
Issues
1. DESCRIBE output for existing user targeting types misses USERS modifier
The DESCRIBE formatter (cmd_workflows.go:426-433) outputs TARGETING MICROFLOW and TARGETING XPATH for existing user-targeting types, but the new syntax supports TARGETING USERS MICROFLOW. While backward-compatible (the grammar makes USERS/GROUPS optional), this creates an asymmetry: group targeting always outputs TARGETING GROUPS MICROFLOW but user targeting never outputs TARGETING USERS MICROFLOW. If a user reads DESCRIBE output, they might not realize the USERS modifier exists. Consider whether DESCRIBE should emit the explicit form for clarity once group targeting is in play.
2. Writer serializes UserTargeting but parser reads both UserSource and UserTargeting — field name inconsistency
The writer (writer_workflow.go) serializes into the UserTargeting BSON field (the current name), which is correct. The parser reads both UserSource (legacy) and UserTargeting (current) — also correct. However, there's no comment or constant explaining which Mendix versions use which field name. The PR description mentions "Mendix 10.12+" for UserTargeting — a brief comment in parser_workflow.go at the fallback would help future maintainers.
3. NoUserTargeting not explicitly handled in parseUserSource
The default case in parseUserSource (parser_workflow.go:598) returns &workflows.NoUserSource{} which implicitly handles Workflows$NoUserTargeting. This works, but adding it as an explicit case alongside NoUserSource would make the code self-documenting and prevent accidental regression if someone later adds logging for the default case.
4. Grammar allows both TARGETING USERS MICROFLOW and TARGETING USERS XPATH on the same task
The grammar rule has two separate optional clauses:
(TARGETING (USERS | GROUPS)? MICROFLOW qualifiedName)?
(TARGETING (USERS | GROUPS)? XPATH STRING_LITERAL)?
This means a user could write both on the same task. The visitor code would set Kind twice (once to microflow, then overwritten by xpath). This is pre-existing behavior, not introduced by this PR, but worth noting.
5. Missing BSON fields for group sources
MicroflowGroupTargeting and XPathGroupTargeting — the parser doesn't read XPath as a fallback field for MicroflowGroupTargeting (unlike the existing MicroflowBasedUserSource which reads MicroflowExpression as fallback). Are there any additional BSON fields on these types? If you've verified against real Studio Pro output, this is fine — but worth confirming.
Positives
- Backward compatibility: Legacy
UserSourcefield is still read, withUserTargetingas fallback — existing MPR files continue to work. - Test coverage: 6 well-structured tests covering all targeting types including legacy.
- Full-stack wiring: Grammar, visitor, AST, executor (read + write), parser, writer, SDK types, and LSP completions all updated consistently.
- Clean scope: Single feature, single commit — follows the PR checklist.
Checklist items to verify
-
make grammarwas run (generated files are included, looks correct) - No
mdl-examples/doctype-tests/test files for the new syntax variants — the PR checklist says MDL syntax changes should include working examples - No version-gating for group targeting — if this requires Mendix 10.12+, should
checkFeature()be called? - Quick reference (
docs/01-project/MDL_QUICK_REFERENCE.md) not updated with the newGROUPSmodifier
Follow-up review after ef0f3e5The new commit adds The substantive items from my previous review still apply:
Otherwise LGTM — the core implementation is solid. |
…labs#169) Parser now reads both legacy UserSource and current UserTargeting BSON fields, recognizing all five $Type variants: MicroflowUserTargeting, XPathUserTargeting, MicroflowGroupTargeting, XPathGroupTargeting, and NoUserTargeting. MDL syntax extended with optional USERS/GROUPS modifier: TARGETING [USERS|GROUPS] MICROFLOW/XPATH.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DESCRIBE output now emits explicit TARGETING USERS for user targeting types (symmetry with TARGETING GROUPS) - Add version history comment for UserSource vs UserTargeting field names - Add explicit NoUserSource/NoUserTargeting case in parseUserSource - Swap XPathConstraint/XPath priority to match writer output - Add doctype-test for all targeting variants - Update quick reference with GROUPS modifier Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ef0f3e5 to
e17e51d
Compare
AI Code ReviewLet me analyze this PR review request carefully. I need to review the PR titled "feat: support full user targeting on workflow USER TASK" against the provided checklist and project context. First, let me understand what this PR does based on the description:
The PR includes changes to:
Now I'll review this against the checklist: Overlap & duplication
MDL syntax designLet me check if the new/ modified MDL syntax follows the guidelines: The PR modifies the syntax from: To: Checking against the guidelines:
Let me check the design-mdl-syntax.md skill as instructed in the checklist: From the checklist: "[ ] Design skill consulted — read I should verify if the syntax follows the principles in that skill. Since I don't have the actual content of that file, I'll rely on the checklist description which says: "New MDL syntax must follow design guidelines: reads as English (target audience is citizen developers), uses standard CRUD verbs (CREATE/ALTER/DROP/SHOW/DESCRIBE), consistent property format (Key: value) for property definitions, AS for name-to-name mappings (old AS new), qualified names (Module.Element), no keyword overloading, no symbolic syntax." Let me evaluate:
Let me check if USERS or GROUPS are overloaded elsewhere in the grammar: Looking at the diff:
I should check if these tokens conflict with existing usage. Since this is a workflow-specific context (inside USER TASK statement), and USERS/GROUPS aren't likely used elsewhere in conflicting ways, this seems acceptable. The guideline says: "IMPORTANT: colon is for property definitions (Key: value), AS is for name-to-name mappings — do not flag AS in mapping contexts as a syntax violation." This doesn't apply here. Full-stack consistency for MDL featuresLet me check if the feature is wired through the full pipeline:
Let me double-check the visitor part. The AST node For the visitor to set these correctly when parsing the new syntax, we would need changes in the visitor code. However, the visitor code isn't shown in the diff. This could be an issue. Actually, looking more carefully at the PR description: "Generated ANTLR parser files (mdl/grammar/parser/) are noise — note but skip." But the visitor is separate from the parser. Let me think about how ANTLR works:
If the AST node changed, we likely need to update the visitor to populate the new fields based on the parsed syntax. Since the visitor files aren't in the diff, this might be a missing piece. However, looking at the executor changes, particularly in func buildUserTask(n *ast.WorkflowUserTaskNode) *workflows.UserTask {
// ...
switch n.Targeting.Kind {
case "microflow":
// ...
case "xpath":
// ...
case "group_microflow":
task.UserSource = &workflows.MicroflowGroupSource{
Microflow: n.Targeting.Microflow.Module + "." + n.Targeting.Microflow.Name,
}
case "group_xpath":
task.UserSource = &workflows.XPathGroupSource{
XPath: n.Targeting.XPath,
}
}
// ...
}This shows that the executor is expecting the visitor to have set:
So the visitor must have been updated to set these values based on whether USERS or GROUPS was present in the TARGETING clause. Since the visitor code isn't in the diff, I need to determine if this is an omission or if the visitor changes were made elsewhere. Actually, looking at the PR diff again, I don't see any changes to mdl/visitor/. This is concerning because if the AST node changed but the visitor wasn't updated, the Kind field wouldn't be set correctly. Let me check if there's a visitor file that would handle this. Typically, for workflow user tasks, there would be something like Since it's not in the diff, this might be a missing piece. Wait, let me look at the AST change again: And the grammar change: So when parsing:
Actually, looking at the Kind values in the comment: "microflow", "xpath", "group_microflow", "group_xpath", or "" This suggests:
But wait, how does the visitor distinguish between legacy TARGETING MICROFLOW and explicit TARGETING USERS MICROFLOW? Both would set Kind="microflow" according to this. Looking at the executor formatUserTask function: switch us := a.UserSource.(type) {
case *workflows.MicroflowBasedUserSource:
if us.Microflow != "" {
lines = append(lines, fmt.Sprintf("%s TARGETING USERS MICROFLOW %s", indent, us.Microflow))
}
case *workflows.XPathBasedUserSource:
if us.XPath != "" {
lines = append(lines, fmt.Sprintf("%s TARGETING USERS XPATH '%s'", indent, us.XPath))
}
case *workflows.MicroflowGroupSource:
if us.Microflow != "" {
lines = append(lines, fmt.Sprintf("%s TARGETING GROUPS MICROFLOW %s", indent, us.Microflow))
}
case *workflows.XPathGroupSource:
if us.XPath != "" {
lines = append(lines, fmt.Sprintf("%s TARGETING GROUPS XPATH '%s'", indent, us.XPath))
}
}This shows that when reading from the model:
But in the AST, we only have one Kind="microflow" for both user and group microflow targeting? That doesn't seem right. Let me re-examine the AST comment: And the executor buildUserTask: switch n.Targeting.Kind {
case "microflow":
task.UserSource = &workflows.MicroflowBasedUserSource{
Microflow: n.Targeting.Microflow.Module + "." + n.Targeting.Microflow.Name,
}
case "xpath":
task.UserSource = &workflows.XPathBasedUserSource{
XPath: n.Targeting.XPath,
}
case "group_microflow":
task.UserSource = &workflows.MicroflowGroupSource{
Microflow: n.Targeting.Microflow.Module + "." + n.Targeting.Microflow.Name,
}
case "group_xpath":
task.UserSource = &workflows.XPathGroupSource{
XPath: n.Targeting.XPath,
}
}This suggests:
But how do we represent the legacy syntax TARGETING MICROFLOW (without USERS)? According to the PR description, this should still be valid and treated as user targeting. Looking at the test file: And in the executor formatUserTask, MicroflowBasedUserSource is formatted as TARGETING USERS MICROFLOW. This creates a problem: when we read a legacy TARGETING MICROFLOW statement, we would store it as MicroflowBasedUserSource, but when we DESCRIBE it back out, we would get TARGETING USERS MICROFLOW, which is not the same as the original. However, the PR description says: "TARGETING MICROFLOW (legacy short form, still valid)" and the test includes it, so it must be handled correctly. Let me check how the visitor would distinguish these cases. The grammar is: So for:
The visitor would need to check if USERS|GROUPS was present and what it was to set the Kind correctly. Given the Kind values in the AST comment, it seems:
But then how does the executor know when to output USERS vs no USERS for the legacy case? Looking at formatUserTask again: case *workflows.MicroflowBasedUserSource:
if us.Microflow != "" {
lines = append(lines, fmt.Sprintf("%s TARGETING USERS MICROFLOW %s", indent, us.Microflow))
}It always outputs USERS for MicroflowBasedUserSource, even if the original was legacy TARGETING MICROFLOW without USERS. This suggests that the legacy form is being normalized to the explicit USERS form when read from the model, which is acceptable if the legacy form is treated as equivalent to TARGETING USERS MICROFLOW. The PR description says: "Parser now reads both legacy Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
…6686) autoBindCallMicroflow() generated the default VoidConditionOutcome inside the microflow lookup loop, after the early return for explicit parameter mappings. When MDL included WITH (...) clauses, the function returned before reaching outcome generation, producing CallMicroflowTask activities with empty Outcomes — triggering CE6686 in Studio Pro. Move outcome generation before the parameter check so it runs unconditionally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AI Code ReviewReview SummaryThis PR adds support for group targeting in workflow user tasks by introducing Critical IssuesNone found Moderate Issues
Minor Issues
What Looks Good
RecommendationApprove the PR. The implementation appears correct and complete based on:
The visitor concern is mitigated by the grammar change making USERS/GROUPS optional modifiers within existing rules (likely compatible with existing visitor logic) and the PR's assertion that the full test suite passes. If any issues arise during testing related to AST construction for the new syntax, they should be addressed then. Automated review via OpenRouter (Nemotron Super 120B) — workflow source |

Fixes #169
Summary
UserSourceand currentUserTargetingBSON fields, recognizing all five$Typevariants (MicroflowUserTargeting,XPathUserTargeting,MicroflowGroupTargeting,XPathGroupTargeting,NoUserTargeting)MicroflowGroupSourceandXPathGroupSourceSDK types with full BSON serialization/deserializationUSERS/GROUPSmodifier:TARGETING [USERS|GROUPS] MICROFLOW/XPATHSyntax
Test plan
mxcli checkvalidates all syntax variants